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 basic ButtonStyle implementation #214

Merged
merged 10 commits into from
Aug 1, 2020
6 changes: 6 additions & 0 deletions NativeDemo/TokamakDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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 */; };
D1C726F324CB63C6003B576D /* ButtonStyleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */; };
D1C726F424CB63C6003B576D /* ButtonStyleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1C726F224CB63C6003B576D /* ButtonStyleDemo.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 */; };
Expand Down Expand Up @@ -101,6 +103,7 @@
B5C76E4924C73ED4003EABB2 /* AppStorageDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorageDemo.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>"; };
D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonStyleDemo.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>"; };
Expand Down Expand Up @@ -162,6 +165,7 @@
85ED189924AD425E0085DFA0 /* TokamakDemo */ = {
isa = PBXGroup;
children = (
D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */,
B5C76E4924C73ED4003EABB2 /* AppStorageDemo.swift */,
B56F22DF24BC89FD001738DF /* ColorDemo.swift */,
85ED189E24AD425E0085DFA0 /* Counter.swift */,
Expand Down Expand Up @@ -335,6 +339,7 @@
3DCDE44424CA6AD400910F17 /* SidebarDemo.swift in Sources */,
85ED18AD24AD425E0085DFA0 /* TextFieldDemo.swift in Sources */,
85ED18A724AD425E0085DFA0 /* ForEachDemo.swift in Sources */,
D1C726F324CB63C6003B576D /* ButtonStyleDemo.swift in Sources */,
854A1A9124B3E3630027BC32 /* ToggleDemo.swift in Sources */,
85ED18A524AD425E0085DFA0 /* TextDemo.swift in Sources */,
85ED18AB24AD425E0085DFA0 /* Counter.swift in Sources */,
Expand All @@ -359,6 +364,7 @@
3DCDE44524CA6AD400910F17 /* SidebarDemo.swift in Sources */,
85ED18AC24AD425E0085DFA0 /* Counter.swift in Sources */,
85ED18A824AD425E0085DFA0 /* ForEachDemo.swift in Sources */,
D1C726F424CB63C6003B576D /* ButtonStyleDemo.swift in Sources */,
854A1A9324B3F28F0027BC32 /* ToggleDemo.swift in Sources */,
85ED18AE24AD425E0085DFA0 /* TextFieldDemo.swift in Sources */,
85ED18A624AD425E0085DFA0 /* TextDemo.swift in Sources */,
Expand Down
31 changes: 0 additions & 31 deletions Sources/TokamakCore/DynamicProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,3 @@ public protocol DynamicProperty {
extension DynamicProperty {
public mutating func update() {}
}

extension TypeInfo {
/// Extract all `DynamicProperty` from a type, recursively.
/// This is necessary as a `DynamicProperty` can be nested.
/// `EnvironmentValues` can also be injected at this point.
func dynamicProperties(_ environment: EnvironmentValues,
Copy link
Collaborator Author

@MaxDesiatov MaxDesiatov Jul 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is moved to MountedElement.swift to maintain just a single extension there.

source: inout Any,
shouldUpdate: Bool) -> [PropertyInfo] {
var dynamicProps = [PropertyInfo]()
for prop in properties where prop.type is DynamicProperty.Type {
dynamicProps.append(prop)
// swiftlint:disable force_try
let propInfo = try! typeInfo(of: prop.type)
propInfo.injectEnvironment(from: environment, into: &source)
var extracted = try! prop.get(from: source)
dynamicProps.append(
contentsOf: propInfo.dynamicProperties(environment,
source: &extracted,
shouldUpdate: shouldUpdate)
)
// swiftlint:disable:next force_cast
var extractedDynamicProp = extracted as! DynamicProperty
if shouldUpdate {
extractedDynamicProp.update()
}
try! prop.set(value: extractedDynamicProp, on: &source)
// swiftlint:enable force_try
}
return dynamicProps
}
}
15 changes: 15 additions & 0 deletions Sources/TokamakCore/Environment/EnvironmentValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ public struct EnvironmentValues: CustomStringConvertible {
}
}

struct IsEnabledKey: EnvironmentKey {
static let defaultValue = true
}

extension EnvironmentValues {
public var isEnabled: Bool {
get {
self[IsEnabledKey.self]
}
set {
self[IsEnabledKey.self] = newValue
}
}
}

struct _EnvironmentValuesWritingModifier: ViewModifier, EnvironmentModifier {
let environmentValues: EnvironmentValues

Expand Down
17 changes: 0 additions & 17 deletions Sources/TokamakCore/MountedViews/MountedApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,3 @@ final class MountedApp<R: Renderer>: MountedCompositeElement<R> {
)
}
}

extension _AnyApp {
func makeMountedApp<R>(
_ parentTarget: R.TargetType,
_ environmentValues: EnvironmentValues
) -> MountedApp<R> where R: Renderer {
// swiftlint:disable:next force_try
let info = try! typeInfo(of: type)

var modified = app
info.injectEnvironment(from: environmentValues, into: &modified)

var result = self
result.app = modified
return MountedApp(result, parentTarget, environmentValues)
}
}
26 changes: 14 additions & 12 deletions Sources/TokamakCore/MountedViews/MountedCompositeElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,15 @@

import OpenCombine

class MountedCompositeElement<R: Renderer>: MountedElement<R>, Hashable {
static func == (lhs: MountedCompositeElement<R>,
rhs: MountedCompositeElement<R>) -> Bool {
lhs === rhs
}

func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}

class MountedCompositeElement<R: Renderer>: MountedElement<R> {
let parentTarget: R.TargetType

var state = [Any]()
var subscriptions = [AnyCancellable]()

init(_ app: _AnyApp, _ parentTarget: R.TargetType, _ environmentValues: EnvironmentValues) {
init<A: App>(_ app: A, _ parentTarget: R.TargetType, _ environmentValues: EnvironmentValues) {
self.parentTarget = parentTarget
super.init(app, environmentValues)
super.init(_AnyApp(app), environmentValues)
}

init(_ scene: _AnyScene, _ parentTarget: R.TargetType, _ environmentValues: EnvironmentValues) {
Expand All @@ -47,3 +38,14 @@ class MountedCompositeElement<R: Renderer>: MountedElement<R>, Hashable {
super.init(view, environmentValues)
}
}

extension MountedCompositeElement: Hashable {
static func == (lhs: MountedCompositeElement<R>,
rhs: MountedCompositeElement<R>) -> Bool {
lhs === rhs
}

func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
107 changes: 72 additions & 35 deletions Sources/TokamakCore/MountedViews/MountedElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ enum MountedElementKind {
case app(_AnyApp)
case scene(_AnyScene)
case view(AnyView)

var type: Any.Type {
switch self {
case let .app(app): return app.type
case let .scene(scene): return scene.type
case let .view(view): return view.type
}
}
}

public class MountedElement<R: Renderer> {
Expand Down Expand Up @@ -65,14 +73,6 @@ public class MountedElement<R: Renderer> {
}
}

var elementType: Any.Type {
switch element {
case let .app(app): return app.type
case let .scene(scene): return scene.type
case let .view(view): return view.type
}
}

var typeConstructorName: String {
switch element {
case .app: fatalError("""
Expand All @@ -91,16 +91,34 @@ public class MountedElement<R: Renderer> {
init(_ app: _AnyApp, _ environmentValues: EnvironmentValues) {
element = .app(app)
self.environmentValues = environmentValues
updateEnvironment()
}

init(_ scene: _AnyScene, _ environmentValues: EnvironmentValues) {
element = .scene(scene)
self.environmentValues = environmentValues
updateEnvironment()
}

init(_ view: AnyView, _ environmentValues: EnvironmentValues) {
element = .view(view)
self.environmentValues = environmentValues
updateEnvironment()
}

@discardableResult func updateEnvironment() -> TypeInfo {
// swiftlint:disable:next force_try
let info = try! typeInfo(of: element.type)
switch element {
case .app:
environmentValues = info.injectEnvironment(from: environmentValues, into: &app.app)
case .scene:
environmentValues = info.injectEnvironment(from: environmentValues, into: &scene.scene)
case .view:
environmentValues = info.injectEnvironment(from: environmentValues, into: &view.view)
}

return info
}

func mount(with reconciler: StackReconciler<R>) {
Expand All @@ -117,29 +135,66 @@ public class MountedElement<R: Renderer> {
}

extension TypeInfo {
func injectEnvironment(from environmentValues: EnvironmentValues, into element: inout Any) {
fileprivate func injectEnvironment(
from environmentValues: EnvironmentValues,
into element: inout Any
) -> EnvironmentValues {
var modifiedEnv = environmentValues
// swiftlint:disable force_try
// Extract the view from the AnyView for modification, apply Environment changes:
if genericTypes.contains(where: { $0 is EnvironmentModifier.Type }),
let modifier = try! property(named: "modifier").get(from: element) as? EnvironmentModifier {
modifier.modifyEnvironment(&modifiedEnv)
}

// Inject @Environment values
// swiftlint:disable force_cast
// swiftlint:disable force_try
// `DynamicProperty`s can have `@Environment` properties contained in them,
// so we have to inject into them as well.
for dynamicProp in properties.filter({ $0.type is DynamicProperty.Type }) {
let propInfo = try! typeInfo(of: dynamicProp.type)
var propWrapper = try! dynamicProp.get(from: element) as! DynamicProperty
for prop in propInfo.properties.filter({ $0.type is EnvironmentReader.Type }) {
var wrapper = try! prop.get(from: propWrapper) as! EnvironmentReader
wrapper.setContent(from: environmentValues)
wrapper.setContent(from: modifiedEnv)
try! prop.set(value: wrapper, on: &propWrapper)
}
try! dynamicProp.set(value: propWrapper, on: &element)
}
for prop in properties.filter({ $0.type is EnvironmentReader.Type }) {
var wrapper = try! prop.get(from: element) as! EnvironmentReader
wrapper.setContent(from: environmentValues)
wrapper.setContent(from: modifiedEnv)
try! prop.set(value: wrapper, on: &element)
}
// swiftlint:enable force_try
// swiftlint:enable force_cast

return modifiedEnv
}

/// Extract all `DynamicProperty` from a type, recursively.
/// This is necessary as a `DynamicProperty` can be nested.
/// `EnvironmentValues` can also be injected at this point.
func dynamicProperties(_ environment: EnvironmentValues,
source: inout Any) -> [PropertyInfo] {
var dynamicProps = [PropertyInfo]()
for prop in properties where prop.type is DynamicProperty.Type {
dynamicProps.append(prop)
// swiftlint:disable force_try
let propInfo = try! typeInfo(of: prop.type)
_ = propInfo.injectEnvironment(from: environment, into: &source)
var extracted = try! prop.get(from: source)
dynamicProps.append(
contentsOf: propInfo.dynamicProperties(environment,
source: &extracted)
)
// swiftlint:disable:next force_cast
var extractedDynamicProp = extracted as! DynamicProperty
extractedDynamicProp.update()
try! prop.set(value: extractedDynamicProp, on: &source)
// swiftlint:enable force_try
}
return dynamicProps
}
}

Expand All @@ -148,30 +203,12 @@ extension AnyView {
_ parentTarget: R.TargetType,
_ environmentValues: EnvironmentValues
) -> MountedElement<R> {
// Find Environment changes
var modifiedEnv = environmentValues
// swiftlint:disable force_try
// Extract the view from the AnyView for modification
let viewInfo = try! typeInfo(of: type)
if viewInfo.genericTypes.filter({ $0 is EnvironmentModifier.Type }).count > 0 {
// Apply Environment changes:
if let modifier = try! viewInfo
.property(named: "modifier")
.get(from: view) as? EnvironmentModifier {
modifier.modifyEnvironment(&modifiedEnv)
}
}
var modifiedView = view
viewInfo.injectEnvironment(from: environmentValues, into: &modifiedView)

var anyView = self
anyView.view = modifiedView
if anyView.type == EmptyView.self {
return MountedEmptyView(anyView, modifiedEnv)
} else if anyView.bodyType == Never.self && !(anyView.type is ViewDeferredToRenderer.Type) {
return MountedHostView(anyView, parentTarget, modifiedEnv)
if type == EmptyView.self {
return MountedEmptyView(self, environmentValues)
} else if bodyType == Never.self && !(type is ViewDeferredToRenderer.Type) {
return MountedHostView(self, parentTarget, environmentValues)
} else {
return MountedCompositeView(anyView, parentTarget, modifiedEnv)
return MountedCompositeView(self, parentTarget, environmentValues)
}
}
}
10 changes: 3 additions & 7 deletions Sources/TokamakCore/MountedViews/MountedHostView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
}

override func mount(with reconciler: StackReconciler<R>) {
guard
let target = reconciler.renderer?.mountTarget(to: parentTarget,
with: self)
guard let target = reconciler.renderer?.mountTarget(to: parentTarget, with: self)
else { return }

self.target = target
Expand Down Expand Up @@ -68,6 +66,7 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
override func update(with reconciler: StackReconciler<R>) {
guard let target = target else { return }

updateEnvironment()
target.view = view
reconciler.renderer?.update(target: target, with: self)

Expand Down Expand Up @@ -96,10 +95,7 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
let newChild: MountedElement<R>
if firstChild.typeConstructorName == mountedChildren[0].view.typeConstructorName {
child.view = firstChild
// Inject Environment
// swiftlint:disable:next force_try
let viewInfo = try! typeInfo(of: child.view.type)
viewInfo.injectEnvironment(from: environmentValues, into: &child.view.view)
child.updateEnvironment()
child.update(with: reconciler)
newChild = child
} else {
Expand Down
17 changes: 4 additions & 13 deletions Sources/TokamakCore/MountedViews/MountedScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,28 +89,19 @@ extension _AnyScene {
_ parentTarget: R.TargetType,
_ environmentValues: EnvironmentValues
) -> MountedScene<R> {
// swiftlint:disable:next force_try
let info = try! typeInfo(of: type)

var modified = scene
info.injectEnvironment(from: environmentValues, into: &modified)

var title: String?
if let titledSelf = modified as? TitledScene,
if let titledSelf = scene as? TitledScene,
let text = titledSelf.title {
title = _TextProxy(text).rawText
}
let children: [MountedElement<R>]
if let deferredScene = modified as? SceneDeferredToRenderer {
if let deferredScene = scene as? SceneDeferredToRenderer {
children = [deferredScene.deferredBody.makeMountedView(parentTarget, environmentValues)]
} else if let groupScene = modified as? GroupScene {
} else if let groupScene = scene as? GroupScene {
children = groupScene.children.map { $0.makeMountedScene(parentTarget, environmentValues) }
} else {
children = []
}

var result = self
result.scene = modified
return .init(result, title, children, parentTarget, environmentValues)
return .init(self, title, children, parentTarget, environmentValues)
}
}
Loading