Add Toggle implementation (#159)

* Fix Button.body

* Add support for renderers customizing default environment values

* Add ParentView conformances

* Add Toggle

* long lines

* Update Path.swift

* Update Path.swift

* Update Sources/TokamakDOM/DOMRenderer.swift

Co-authored-by: Max Desiatov <max@desiatov.com>

* bodyBuild → bodyClosure

* Update progress.md

* Update progress.md, implement Toggle(_ configuration: ToggleStyleConfiguration)

* Fix demo on native

* Hopefully fix issue

* Hopefully fix issue for real

* maybe this will work

* Update ToggleDemo.swift

* AnyToggleStyle → _AnyToggleStyle

* Fix remaining AnyToggleStyle

* Clean up unnecessary files

* Move typealias to Core.swift

* Revert change to ListDemo, remove unused let

Co-authored-by: Max Desiatov <max@desiatov.com>
This commit is contained in:
Jed Fox 2020-07-20 18:21:32 -04:00 committed by GitHub
parent 5f3822257d
commit f0e2b054dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 249 additions and 21 deletions

View File

@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
854A1A9124B3E3630027BC32 /* ToggleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CBD5DE24B3BF090066468A /* ToggleDemo.swift */; };
854A1A9324B3F28F0027BC32 /* ToggleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CBD5DE24B3BF090066468A /* ToggleDemo.swift */; };
85ED186A24AD38F20085DFA0 /* UIAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85ED186924AD38F20085DFA0 /* UIAppDelegate.swift */; };
85ED188A24AD3CD60085DFA0 /* macOS.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85ED188724AD3CC30085DFA0 /* macOS.storyboard */; };
85ED188C24AD3CF10085DFA0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85ED188B24AD3CF10085DFA0 /* LaunchScreen.storyboard */; };
@ -72,6 +74,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
85CBD5DE24B3BF090066468A /* ToggleDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToggleDemo.swift; sourceTree = "<group>"; };
85ED184A24AD379A0085DFA0 /* TokamakDemo Native.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TokamakDemo Native.app"; sourceTree = BUILT_PRODUCTS_DIR; };
85ED185224AD379A0085DFA0 /* TokamakDemo Native.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TokamakDemo Native.app"; sourceTree = BUILT_PRODUCTS_DIR; };
85ED186924AD38F20085DFA0 /* UIAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIAppDelegate.swift; sourceTree = "<group>"; };
@ -162,6 +165,7 @@
85ED189D24AD425E0085DFA0 /* TokamakDemo.swift */,
85ED189E24AD425E0085DFA0 /* Counter.swift */,
85ED189F24AD425E0085DFA0 /* TextFieldDemo.swift */,
85CBD5DE24B3BF090066468A /* ToggleDemo.swift */,
85ED18A024AD425E0085DFA0 /* EnvironmentDemo.swift */,
B51F214F24B920B400CF2583 /* PathDemo.swift */,
B56F22DF24BC89FD001738DF /* ColorDemo.swift */,
@ -321,6 +325,7 @@
85ED18A924AD425E0085DFA0 /* TokamakDemo.swift in Sources */,
85ED18AD24AD425E0085DFA0 /* TextFieldDemo.swift in Sources */,
85ED18A724AD425E0085DFA0 /* ForEachDemo.swift in Sources */,
854A1A9124B3E3630027BC32 /* ToggleDemo.swift in Sources */,
85ED18A524AD425E0085DFA0 /* TextDemo.swift in Sources */,
85ED18AB24AD425E0085DFA0 /* Counter.swift in Sources */,
);
@ -342,6 +347,7 @@
85ED18B624AD42D70085DFA0 /* NSAppDelegate.swift in Sources */,
85ED18AC24AD425E0085DFA0 /* Counter.swift in Sources */,
85ED18A824AD425E0085DFA0 /* ForEachDemo.swift in Sources */,
854A1A9324B3F28F0027BC32 /* ToggleDemo.swift in Sources */,
85ED18AE24AD425E0085DFA0 /* TextFieldDemo.swift in Sources */,
85ED18A624AD425E0085DFA0 /* TextDemo.swift in Sources */,
);
@ -490,7 +496,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = 288H3WAR3W;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "iOS Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -512,7 +518,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = 288H3WAR3W;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "iOS Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;

View File

@ -86,9 +86,11 @@ public struct Path: Equatable, LosslessStringConvertible {
public init(roundedRect rect: CGRect,
cornerSize: CGSize,
style: RoundedCornerStyle = .circular) {
storage = .roundedRect(FixedRoundedRect(rect: rect,
cornerSize: cornerSize,
style: style))
storage = .roundedRect(FixedRoundedRect(
rect: rect,
cornerSize: cornerSize,
style: style
))
}
public init(roundedRect rect: CGRect,

View File

@ -29,13 +29,14 @@ public final class StackReconciler<R: Renderer> {
view: V,
target: R.TargetType,
renderer: R,
environment: EnvironmentValues,
scheduler: @escaping (@escaping () -> ()) -> ()
) {
self.renderer = renderer
self.scheduler = scheduler
rootTarget = target
rootView = view.makeMountedView(target, EnvironmentValues())
rootView = view.makeMountedView(target, environment)
rootView.mount(with: self)
}

View File

@ -0,0 +1,76 @@
// 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 Jed Fox on 07/04/2020.
//
// swiftlint:disable line_length
// Adapted from https://github.com/SwiftWebUI/SwiftWebUI/blob/16b84d46/Sources/SwiftWebUI/Views/Forms/Toggle.swift
// swiftlint:enable line_length
//
// NOTE: ToggleStyleConfiguration.label is supposed to be a special Never View.
// It seems like during the rendering process its dynamically replaced with the actual label.
// Thats complicated so instead were providing the label view directly.
public struct ToggleStyleConfiguration {
public let label: AnyView
@Binding public var isOn: Swift.Bool
}
public protocol ToggleStyle {
associatedtype Body: View
func makeBody(configuration: Self.Configuration) -> Self.Body
typealias Configuration = ToggleStyleConfiguration
}
public struct _AnyToggleStyle: ToggleStyle {
public typealias Body = AnyView
private let bodyClosure: (ToggleStyleConfiguration) -> AnyView
public init<S: ToggleStyle>(_ style: S) {
bodyClosure = { configuration in
AnyView(style.makeBody(configuration: configuration))
}
}
public func makeBody(configuration: ToggleStyleConfiguration) -> AnyView {
bodyClosure(configuration)
}
}
public enum ToggleStyleKey: EnvironmentKey {
public static var defaultValue: _AnyToggleStyle {
fatalError("\(self) must have a renderer-provided default value")
}
}
extension EnvironmentValues {
var toggleStyle: _AnyToggleStyle {
get {
self[ToggleStyleKey.self]
}
set {
self[ToggleStyleKey.self] = newValue
}
}
}
extension View {
public func toggleStyle<S>(_ style: S) -> some View where S: ToggleStyle {
environment(\.toggleStyle, _AnyToggleStyle(style))
}
}

View File

@ -46,7 +46,7 @@ public struct Button<Label>: View where Label: View {
}
public var body: Never {
neverBody("Text")
neverBody("Button")
}
}

View File

@ -56,6 +56,12 @@ extension SecureField where Label == Text {
}
}
extension SecureField: ParentView {
public var children: [AnyView] {
(label as? GroupView)?.children ?? [AnyView(label)]
}
}
/// This is a helper class that works around absence of "package private" access control in Swift
public struct _SecureFieldProxy {
public let subject: SecureField<Text>

View File

@ -67,6 +67,12 @@ extension TextField where Label == Text {
// ) where S : StringProtocol
}
extension TextField: ParentView {
public var children: [AnyView] {
(label as? GroupView)?.children ?? [AnyView(label)]
}
}
/// This is a helper class that works around absence of "package private" access control in Swift
public struct _TextFieldProxy {
public let subject: TextField<Text>

View File

@ -0,0 +1,54 @@
// Copyright 2018-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 Jed Fox on 07/04/2020.
//
public struct Toggle<Label>: View where Label: View {
@Binding var isOn: Bool
var label: Label
@Environment(\.toggleStyle) var toggleStyle: _AnyToggleStyle
public init(isOn: Binding<Bool>, label: () -> Label) {
_isOn = isOn
self.label = label()
}
public var body: AnyView {
toggleStyle.makeBody(
configuration: ToggleStyleConfiguration(label: AnyView(label), isOn: $isOn)
)
}
}
extension Toggle where Label == Text {
public init<S>(_ title: S, isOn: Binding<Bool>) where S: StringProtocol {
self.init(isOn: isOn) {
Text(title)
}
}
}
extension Toggle where Label == AnyView {
public init(_ configuration: ToggleStyleConfiguration) {
label = configuration.label
_isOn = configuration.$isOn
}
}
extension Toggle: ParentView {
public var children: [AnyView] {
(label as? GroupView)?.children ?? [AnyView(label)]
}
}

View File

@ -91,6 +91,7 @@ public typealias SecureField = TokamakCore.SecureField
public typealias Spacer = TokamakCore.Spacer
public typealias Text = TokamakCore.Text
public typealias TextField = TokamakCore.TextField
public typealias Toggle = TokamakCore.Toggle
public typealias VStack = TokamakCore.VStack
public typealias ZStack = TokamakCore.ZStack

View File

@ -91,10 +91,14 @@ public final class DOMRenderer: Renderer {
rootStyle.innerHTML = .string(tokamakStyles)
_ = head.appendChild!(rootStyle)
var environment = EnvironmentValues()
environment[ToggleStyleKey] = _AnyToggleStyle(DefaultToggleStyle())
reconciler = StackReconciler(
view: view,
target: DOMNode(view, ref),
renderer: self
renderer: self,
environment: environment
) { closure in
let fn = JSClosure { _ in
closure()

View File

@ -0,0 +1,49 @@
// 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 Jed Fox on 07/04/2020.
//
import TokamakCore
public struct DefaultToggleStyle: ToggleStyle {
public func makeBody(configuration: Configuration) -> some View {
CheckboxToggleStyle().makeBody(configuration: configuration)
}
}
public struct CheckboxToggleStyle: ToggleStyle {
public func makeBody(configuration: ToggleStyleConfiguration) -> some View {
var attrs = ["type": "checkbox"]
if configuration.isOn {
attrs["checked"] = "checked"
}
return HTML("label") {
HTML("input", attrs, listeners: [
"change": { event in
let checked = event.target.object?.checked.boolean ?? false
configuration.isOn = checked
},
])
configuration.label
}
}
}
// FIXME: implement properly
public struct SwitchToggleStyle: ToggleStyle {
public func makeBody(configuration: Configuration) -> some View {
CheckboxToggleStyle().makeBody(configuration: configuration)
}
}

View File

@ -18,17 +18,6 @@
import TokamakShim
public struct ListDemo: View {
let fs: [File] = [
.init(id: 0, name: "Users", children: [
.init(id: 1, name: "carson", children: [
.init(id: 2, name: "home", children: [
.init(id: 3, name: "Documents", children: nil),
.init(id: 4, name: "Desktop", children: nil),
]),
]),
]),
]
public var body: some View {
List {
ForEach(0..<3) {

View File

@ -0,0 +1,33 @@
// 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 canImport(SwiftUI)
import SwiftUI
#else
import TokamakCore
import TokamakDOM
#endif
public struct ToggleDemo: View {
@State var checked = false
public var body: some View {
VStack {
Toggle("Check me!", isOn: $checked)
Toggle(isOn: Binding(get: { true }, set: { _ in })) {
Text("Im always checked!").foregroundColor(.red).italic()
}
}
}
}

View File

@ -19,7 +19,7 @@ import TokamakShim
struct TokamakDemoView: View {
var body: some View {
ScrollView(showsIndicators: false) {
ScrollView(showsIndicators: true) {
HStack {
Spacer()
}
@ -40,6 +40,7 @@ struct TokamakDemoView: View {
Group {
ForEachDemo()
TextDemo()
ToggleDemo()
PathDemo()
TextFieldDemo()
SpacerDemo()

View File

@ -43,7 +43,7 @@ Table columns:
| | | |
| --- | ---------------------------------------------------------------------------- | :-: |
| | [Toggle](https://developer.apple.com/documentation/swiftui/toggle) | |
| 🚧 | [Toggle](https://developer.apple.com/documentation/swiftui/toggle) | |
| 🚧 | [Picker](https://developer.apple.com/documentation/swiftui/picker) | |
| | [DatePicker](https://developer.apple.com/documentation/swiftui/datepicker) | |
| | [Slider](https://developer.apple.com/documentation/swiftui/slider) | |