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 Picker and PopUpButtonPickerStyle as default #149

Merged
merged 15 commits into from
Jul 17, 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
46 changes: 46 additions & 0 deletions Sources/TokamakCore/Styles/PickerStyle.swift
Original file line number Diff line number Diff line change
@@ -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.

public protocol PickerStyle {}
j-f1 marked this conversation as resolved.
Show resolved Hide resolved

public struct PopUpButtonPickerStyle: PickerStyle {}

public struct RadioGroupPickerStyle: PickerStyle {}

public struct SegmentedPickerStyle: PickerStyle {}

public struct WheelPickerStyle: PickerStyle {}

public struct DefaultPickerStyle: PickerStyle {}

enum PickerStyleKey: EnvironmentKey {
static var defaultValue: PickerStyle = DefaultPickerStyle()
}

extension EnvironmentValues {
var pickerStyle: PickerStyle {
get {
self[PickerStyleKey.self]
}
set {
self[PickerStyleKey.self] = newValue
}
}
}

extension View {
public func pickerStyle(_ style: PickerStyle) -> some View {
environment(\.pickerStyle, style)
}
}
11 changes: 11 additions & 0 deletions Sources/TokamakCore/Views/ForEach.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

/// A protocol that allows matching against type-erased `ForEach` at run time.
protocol ForEachProtocol: GroupView {
var elementType: Any.Type { get }
func element(at: Int) -> Any
}

/// A structure that computes `View`s from a collection of identified data.
///
/// Available when `Data` conforms to `RandomAccessCollection`,
Expand Down Expand Up @@ -46,6 +52,11 @@ public struct ForEach<Data, ID, Content>: View
}
}

extension ForEach: ForEachProtocol where Data.Index == Int {
var elementType: Any.Type { Data.Element.self }
func element(at index: Int) -> Any { data[index] }
}

public extension ForEach where Data.Element: Identifiable, ID == Data.Element.ID {
init(
_ data: Data,
Expand Down
5 changes: 2 additions & 3 deletions Sources/TokamakCore/Views/Group.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@

public struct Group<Content> {
let content: Content
}

extension Group: View where Content: View {
public init(@ViewBuilder content: () -> Content) {
self.content = content()
}
}

extension Group: View where Content: View {
public var body: Never {
neverBody("Group")
}
Expand Down
100 changes: 100 additions & 0 deletions Sources/TokamakCore/Views/Picker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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 _PickerContainer<Label: View, SelectionValue: Hashable, Content: View>: View {
@Binding public var selection: SelectionValue
public let label: Label
public let content: Content
@Environment(\.pickerStyle) public var style: PickerStyle

public init(
selection: Binding<SelectionValue>,
label: Label,
@ViewBuilder content: () -> Content
) {
_selection = selection
self.label = label
self.content = content()
}

public var body: Never {
neverBody("_PickerLabel")
}
}

public struct _PickerElement: View {
public let valueIndex: Int?
public let content: AnyView
@Environment(\.pickerStyle) public var style: PickerStyle

public var body: Never {
neverBody("_PickerElement")
}
}

public struct Picker<Label: View, SelectionValue: Hashable, Content: View>: View {
let selection: Binding<SelectionValue>
let label: Label
let content: Content

public init(
selection: Binding<SelectionValue>,
label: Label,
@ViewBuilder content: () -> Content
) {
self.selection = selection
self.label = label
self.content = content()
}

public var body: some View {
let children = self.children

return _PickerContainer(selection: selection, label: label) {
// Need to implement a special behavior here. If one of the children is `ForEach`
// and its `Data.Element` type is the same as `SelectionValue` type, then we can
// update the binding.
ForEach(0..<children.count) { index in
if let forEach = mapAnyView(children[index], transform: { (v: ForEachProtocol) in v }),
forEach.elementType == SelectionValue.self {
let nestedChildren = forEach.children

ForEach(0..<nestedChildren.count) { nestedIndex in
_PickerElement(valueIndex: nestedIndex, content: nestedChildren[nestedIndex])
}
} else {
_PickerElement(valueIndex: nil, content: children[index])
}
}
}
}
}

extension Picker where Label == Text {
@_disfavoredOverload public init<S: StringProtocol>(
_ title: S,
selection: Binding<SelectionValue>,
@ViewBuilder content: () -> Content
) {
self.init(selection: selection, label: Text(title)) {
content()
}
}
}

extension Picker: ParentView {
public var children: [AnyView] {
(content as? GroupView)?.children ?? [AnyView(content)]
}
}
3 changes: 2 additions & 1 deletion Sources/TokamakCore/Views/TextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public struct TextField<Label>: View where Label: View {

extension TextField where Label == Text {
public init<S>(
_ title: S, text: Binding<String>,
_ title: S,
text: Binding<String>,
onEditingChanged: @escaping (Bool) -> () = { _ in },
onCommit: @escaping () -> () = {}
) where S: StringProtocol {
Expand Down
56 changes: 56 additions & 0 deletions Sources/TokamakDOM/Views/Picker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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

public typealias Picker = TokamakCore.Picker
public typealias PopUpButtonPickerStyle = TokamakCore.PopUpButtonPickerStyle
public typealias RadioGroupPickerStyle = TokamakCore.RadioGroupPickerStyle
public typealias SegmentedPickerStyle = TokamakCore.SegmentedPickerStyle
public typealias WheelPickerStyle = TokamakCore.WheelPickerStyle
public typealias DefaultPickerStyle = TokamakCore.DefaultPickerStyle

extension _PickerContainer: ViewDeferredToRenderer {
public var deferredBody: AnyView {
AnyView(HTML("label") {
label
Text(" ")
HTML("select", listeners: ["change": {
guard
let valueString = $0.target.object!.value.string,
let value = Int(valueString) as? SelectionValue
else { return }
selection = value
}]) {
content
}
})
}
}

extension _PickerElement: ViewDeferredToRenderer {
public var deferredBody: AnyView {
let attributes: [String: String]
if let value = valueIndex {
attributes = ["value": "\(value)"]
} else {
attributes = [:]
}

return AnyView(HTML("option", attributes) {
content
})
}
}
4 changes: 2 additions & 2 deletions Sources/TokamakDOM/Views/TextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public typealias TextField = TokamakCore.TextField
func css(for style: TextFieldStyle) -> String {
if style is PlainTextFieldStyle {
return """
background: transparent;
border: none;
background: transparent;
border: none;
"""
} else {
return ""
Expand Down
20 changes: 11 additions & 9 deletions Sources/TokamakDemo/GridDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ public struct GridDemo: View {
}
VStack {
Text("Simple LazyHGrid")
LazyHGrid(rows: [
GridItem(.fixed(50)),
GridItem(.fixed(50)),
GridItem(.fixed(50)),
]) {
ForEach(0..<45) {
Text("\($0 + 1)")
.padding()
.background(Color.blue)
ScrollView(.horizontal) {
LazyHGrid(rows: [
GridItem(.fixed(50)),
GridItem(.fixed(50)),
GridItem(.fixed(50)),
]) {
ForEach(0..<45) {
Text("\($0 + 1)")
.padding()
.background(Color.blue)
}
}
}
}
Expand Down
1 change: 0 additions & 1 deletion Sources/TokamakDemo/PathDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ struct Star: Shape {
path.addLine(to: .init(x: 0, y: 30.4))
path.addLine(to: .init(x: 64, y: 76))
path.addLine(to: .init(x: 40, y: 0))
print(path)
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions Sources/TokamakDemo/PickerDemo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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 TokamakDOM
#endif

struct PickerDemo: View {
var textStyles = Font.TextStyle.allCases

@State private var selection = 0

var body: some View {
Picker(selection: $selection, label: Text("Text style")
.font(.system(textStyles[selection]))) {
Text("Pick a text style...")
ForEach(0..<textStyles.count) {
Text(String(describing: textStyles[$0]))
}
}
}
}
1 change: 1 addition & 0 deletions Sources/TokamakDemo/TokamakDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct TokamakDemoView: View {
SpacerDemo()
EnvironmentDemo()
.font(.system(size: 8))
PickerDemo()
}
Group {
#if canImport(TokamakDOM)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
D1B4229124B3B9BB00682F74 /* ListDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228E24B3B9BB00682F74 /* ListDemo.swift */; };
D1B4229224B3B9BB00682F74 /* OutlineGroupDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228F24B3B9BB00682F74 /* OutlineGroupDemo.swift */; };
D1B4229324B3B9BB00682F74 /* OutlineGroupDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228F24B3B9BB00682F74 /* OutlineGroupDemo.swift */; };
D1EE7EA724C0DD2100C0D127 /* PickerDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1EE7EA624C0DD2100C0D127 /* PickerDemo.swift */; };
D1EE7EA824C0DD2100C0D127 /* PickerDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1EE7EA624C0DD2100C0D127 /* PickerDemo.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -58,6 +60,7 @@
B56F22E224BD1C26001738DF /* GridDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridDemo.swift; sourceTree = "<group>"; };
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>"; };
D1EE7EA624C0DD2100C0D127 /* PickerDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickerDemo.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -104,6 +107,7 @@
85ED189924AD425E0085DFA0 /* TokamakDemo */ = {
isa = PBXGroup;
children = (
D1EE7EA624C0DD2100C0D127 /* PickerDemo.swift */,
D1B4228E24B3B9BB00682F74 /* ListDemo.swift */,
D1B4228F24B3B9BB00682F74 /* OutlineGroupDemo.swift */,
85ED189A24AD425E0085DFA0 /* SpacerDemo.swift */,
Expand Down Expand Up @@ -228,6 +232,7 @@
85ED18AF24AD425E0085DFA0 /* EnvironmentDemo.swift in Sources */,
85ED18A324AD425E0085DFA0 /* SpacerDemo.swift in Sources */,
D1B4229024B3B9BB00682F74 /* ListDemo.swift in Sources */,
D1EE7EA724C0DD2100C0D127 /* PickerDemo.swift in Sources */,
85ED18A924AD425E0085DFA0 /* TokamakDemo.swift in Sources */,
85ED18AD24AD425E0085DFA0 /* TextFieldDemo.swift in Sources */,
85ED18A724AD425E0085DFA0 /* ForEachDemo.swift in Sources */,
Expand All @@ -248,6 +253,7 @@
85ED18A424AD425E0085DFA0 /* SpacerDemo.swift in Sources */,
85ED18B024AD425E0085DFA0 /* EnvironmentDemo.swift in Sources */,
D1B4229124B3B9BB00682F74 /* ListDemo.swift in Sources */,
D1EE7EA824C0DD2100C0D127 /* PickerDemo.swift in Sources */,
85ED18B624AD42D70085DFA0 /* NSAppDelegate.swift in Sources */,
85ED18AC24AD425E0085DFA0 /* Counter.swift in Sources */,
85ED18A824AD425E0085DFA0 /* ForEachDemo.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion docs/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Table columns:
| | | |
| --- | ---------------------------------------------------------------------------- | :-: |
| | [Toggle](https://developer.apple.com/documentation/swiftui/toggle) | |
| | [Picker](https://developer.apple.com/documentation/swiftui/picker) | |
| 🚧 | [Picker](https://developer.apple.com/documentation/swiftui/picker) | |
| | [DatePicker](https://developer.apple.com/documentation/swiftui/datepicker) | |
| | [Slider](https://developer.apple.com/documentation/swiftui/slider) | |
| | [Stepper](https://developer.apple.com/documentation/swiftui/stepper) | |
Expand Down