Migrate reconciler and TestRenderer to View API (#107)
* Migrate reconciler and AppKit renderer to View API
* Fix building tests, cleanup unused code
* Remove more unused code
* Remove TokamakAppKit, add ParentView/GroupView
* Remove unused code, make testMount pass
* Cleanup more unused code
* Add ValueStorage protocol
* Add getter/setter internal properties on State
* Fix more State test assertions
* Fix all the reconciler tests 🎉
This commit is contained in:
parent
3d714964e6
commit
9407dd0674
|
@ -6,4 +6,5 @@
|
|||
--operatorfunc nospace
|
||||
--ifdef noindent
|
||||
--stripunusedargs closure-only
|
||||
--disable andOperator
|
||||
--disable andOperator
|
||||
--swiftversion 5.2
|
||||
|
|
|
@ -18,7 +18,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
func application(_: UIApplication, didFinishLaunchingWithOptions _: Options?)
|
||||
-> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
true
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_: UIApplication) {
|
||||
|
|
|
@ -30,7 +30,7 @@ enum ElementaryParticles: String, CaseIterable {
|
|||
}
|
||||
|
||||
extension ElementaryParticles: CustomStringConvertible {
|
||||
var description: String { return rawValue.localizedCapitalized }
|
||||
var description: String { rawValue.localizedCapitalized }
|
||||
}
|
||||
|
||||
private struct Cells: CellProvider {
|
||||
|
@ -39,7 +39,7 @@ private struct Cells: CellProvider {
|
|||
item: ElementaryParticles,
|
||||
path: CellPath
|
||||
) -> AnyNode {
|
||||
return Label.node(.init(
|
||||
Label.node(.init(
|
||||
Style(
|
||||
[CenterY.equal(to: .parent),
|
||||
Height.equal(to: 44),
|
||||
|
@ -59,7 +59,7 @@ struct CollectionExample: PureLeafComponent {
|
|||
typealias Props = Null
|
||||
|
||||
static func render(props: Props) -> AnyNode {
|
||||
return CollectionView<Cells>.node(.init(
|
||||
CollectionView<Cells>.node(.init(
|
||||
Style(
|
||||
Edges.equal(to: .parent, inset: 20),
|
||||
backgroundColor: .white
|
||||
|
|
|
@ -13,7 +13,7 @@ class ScrollDelegate: NSObject, UIScrollViewDelegate {
|
|||
var view: UIView?
|
||||
|
||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||
return view
|
||||
view
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ private struct Cells: CellProvider {
|
|||
item: AppRoute,
|
||||
path: CellPath
|
||||
) -> AnyNode {
|
||||
return Label.node(.init(
|
||||
Label.node(.init(
|
||||
Style(
|
||||
[CenterY.equal(to: .parent),
|
||||
Height.equal(to: 44),
|
||||
|
@ -37,7 +37,7 @@ struct List: PureLeafComponent {
|
|||
}
|
||||
|
||||
static func render(props: Props) -> AnyNode {
|
||||
return ListView<Cells>.node(.init(
|
||||
ListView<Cells>.node(.init(
|
||||
Style([
|
||||
Edges.equal(to: .parent),
|
||||
]),
|
||||
|
|
|
@ -15,7 +15,7 @@ struct NavigationModal: PureLeafComponent {
|
|||
}
|
||||
|
||||
static func render(props: Props) -> AnyNode {
|
||||
return props.isPresented.value ?
|
||||
props.isPresented.value ?
|
||||
ModalPresenter.node(
|
||||
NavigationPresenter<ModalRouter>.node(
|
||||
.init(
|
||||
|
@ -36,7 +36,7 @@ struct SimpleModal: PureLeafComponent {
|
|||
}
|
||||
|
||||
static func render(props: Props) -> AnyNode {
|
||||
return props.isPresented.value ? ModalPresenter.node(
|
||||
props.isPresented.value ? ModalPresenter.node(
|
||||
Animation.node(
|
||||
Null(),
|
||||
Button.node(.init(
|
||||
|
|
|
@ -13,7 +13,7 @@ struct ScrollViewExample: LeafComponent {
|
|||
typealias Props = Null
|
||||
|
||||
static func render(props: Props, hooks: Hooks) -> AnyNode {
|
||||
return View.node(
|
||||
View.node(
|
||||
.init(Style(Edges.equal(to: .safeArea))),
|
||||
ScrollView.node(
|
||||
.init(Style(Edges.equal(to: .parent))),
|
||||
|
|
|
@ -14,7 +14,7 @@ struct ListProvider: CellProvider {
|
|||
typealias Model = Int
|
||||
|
||||
static func cell(props _: Null, item: Int, path _: CellPath) -> AnyNode {
|
||||
return Label.node(.init(
|
||||
Label.node(.init(
|
||||
Style(
|
||||
[CenterY.equal(to: .parent),
|
||||
Height.equal(to: 44),
|
||||
|
|
|
@ -13,17 +13,16 @@ struct ThrobberExample: PureLeafComponent {
|
|||
typealias Props = Null
|
||||
|
||||
static func render(props: Null) -> AnyNode {
|
||||
return
|
||||
View.node(
|
||||
.init(Style(
|
||||
Edges.equal(to: .parent),
|
||||
backgroundColor: .black
|
||||
)),
|
||||
Throbber.node(.init(
|
||||
Style(Edges.equal(to: .parent)),
|
||||
isAnimating: true,
|
||||
variety: .whiteLarge
|
||||
))
|
||||
)
|
||||
View.node(
|
||||
.init(Style(
|
||||
Edges.equal(to: .parent),
|
||||
backgroundColor: .black
|
||||
)),
|
||||
Throbber.node(.init(
|
||||
Style(Edges.equal(to: .parent)),
|
||||
isAnimating: true,
|
||||
variety: .whiteLarge
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ enum AppRoute: String, CaseIterable {
|
|||
}
|
||||
|
||||
extension AppRoute: CustomStringConvertible {
|
||||
var description: String { return rawValue.localizedCapitalized }
|
||||
var description: String { rawValue.localizedCapitalized }
|
||||
}
|
||||
|
||||
struct Router: NavigationRouter {
|
||||
|
|
|
@ -13,7 +13,7 @@ import UIKit
|
|||
|
||||
final class ViewController: TokamakViewController {
|
||||
override var node: AnyNode {
|
||||
return NavigationPresenter<Router>.node(.init(initial: .list))
|
||||
NavigationPresenter<Router>.node(.init(initial: .list))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import TokamakDemo
|
|||
|
||||
final class ViewController: TokamakViewController {
|
||||
override var node: AnyNode {
|
||||
return View.node(
|
||||
View.node(
|
||||
.init(Style([
|
||||
Edges.equal(to: .parent),
|
||||
Width.equal(to: 200),
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "Runtime",
|
||||
"repositoryURL": "https://github.com/wickwirew/Runtime.git",
|
||||
"state": {
|
||||
"branch": "master",
|
||||
"revision": "0a059a96c49b4e8bca085476bc8dc2061921c5b6",
|
||||
"version": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
|
@ -16,10 +16,6 @@ let package = Package(
|
|||
name: "Tokamak",
|
||||
targets: ["Tokamak"]
|
||||
),
|
||||
.library(
|
||||
name: "TokamakAppKit",
|
||||
targets: ["TokamakAppKit"]
|
||||
),
|
||||
.library(
|
||||
name: "TokamakTestRenderer",
|
||||
targets: ["TokamakTestRenderer"]
|
||||
|
@ -28,6 +24,7 @@ let package = Package(
|
|||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
.package(url: "https://github.com/wickwirew/Runtime.git", .branch("master")),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define
|
||||
|
@ -36,23 +33,19 @@ let package = Package(
|
|||
// in packages which this package depends on.
|
||||
.target(
|
||||
name: "Tokamak",
|
||||
dependencies: []
|
||||
dependencies: ["Runtime"]
|
||||
),
|
||||
.target(
|
||||
name: "TokamakDemo",
|
||||
dependencies: ["Tokamak"]
|
||||
),
|
||||
.target(
|
||||
name: "TokamakAppKit",
|
||||
dependencies: ["Tokamak"]
|
||||
),
|
||||
.target(
|
||||
name: "TokamakTestRenderer",
|
||||
dependencies: ["Tokamak"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TokamakTests",
|
||||
dependencies: ["TokamakTestRenderer"]
|
||||
dependencies: ["TokamakDemo", "TokamakTestRenderer"]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
|
|
@ -26,6 +26,6 @@ public struct AnyEquatable: Equatable {
|
|||
}
|
||||
|
||||
public static func ==(lhs: AnyEquatable, rhs: AnyEquatable) -> Bool {
|
||||
return lhs.equals(rhs.value) || rhs.equals(lhs.value)
|
||||
lhs.equals(rhs.value) || rhs.equals(lhs.value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
//
|
||||
// Node.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 30/11/2018.
|
||||
//
|
||||
|
||||
public struct AnyNode: Equatable {
|
||||
// Equatable can't be automatically derived for `type` property?
|
||||
public static func ==(lhs: AnyNode, rhs: AnyNode) -> Bool {
|
||||
return
|
||||
|
||||
lhs.type == rhs.type &&
|
||||
lhs.children == rhs.children &&
|
||||
lhs.props == rhs.props &&
|
||||
lhs.ref === rhs.ref
|
||||
}
|
||||
|
||||
public let props: AnyEquatable
|
||||
public let children: AnyEquatable
|
||||
let type: ComponentType
|
||||
public let ref: AnyObject?
|
||||
|
||||
public func isSubtypeOf<T>(_: T.Type) -> Bool {
|
||||
return type.host is T.Type || type.composite is T.Type
|
||||
}
|
||||
|
||||
public func isSubtypeOf<T, U>(_: T.Type, or: U.Type) -> Bool {
|
||||
return isSubtypeOf(T.self) || isSubtypeOf(U.self)
|
||||
}
|
||||
|
||||
public func isSubtypeOf<T, U, V>(
|
||||
_: T.Type,
|
||||
or _: U.Type,
|
||||
or _: V.Type
|
||||
) -> Bool {
|
||||
return isSubtypeOf(T.self) || isSubtypeOf(U.self) || isSubtypeOf(V.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Null {
|
||||
public static func node() -> AnyNode {
|
||||
return AnyNode(
|
||||
props: AnyEquatable(Null()),
|
||||
children: AnyEquatable(Null()),
|
||||
type: .null,
|
||||
ref: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Component where Children == Null {
|
||||
public static func node(_ props: Props) -> AnyNode {
|
||||
return node(props, Null())
|
||||
}
|
||||
}
|
||||
|
||||
extension Component where Props == Null, Children == Null {
|
||||
public static func node() -> AnyNode {
|
||||
return node(Null(), Null())
|
||||
}
|
||||
}
|
||||
|
||||
extension Component where Props: Default, Props.DefaultValue == Props {
|
||||
public static func node(_ children: Children) -> AnyNode {
|
||||
return node(Props.defaultValue, children)
|
||||
}
|
||||
}
|
||||
|
||||
extension Component where Children == [AnyNode] {
|
||||
public static func node(_ props: Props,
|
||||
_ child: AnyNode) -> AnyNode {
|
||||
return node(props, [child])
|
||||
}
|
||||
|
||||
public static func node(_ props: Props) -> AnyNode {
|
||||
return node(props, [])
|
||||
}
|
||||
}
|
||||
|
||||
extension Component where Props == Null, Children == [AnyNode] {
|
||||
public static func node() -> AnyNode {
|
||||
return node(Null(), [])
|
||||
}
|
||||
}
|
||||
|
||||
extension Component where Props: Default, Props.DefaultValue == Props,
|
||||
Children == [AnyNode] {
|
||||
public static func node(_ child: AnyNode) -> AnyNode {
|
||||
return node(Props.defaultValue, [child])
|
||||
}
|
||||
|
||||
public static func node() -> AnyNode {
|
||||
return node(Props.defaultValue, [])
|
||||
}
|
||||
}
|
||||
|
||||
extension HostComponent {
|
||||
public static func node(_ props: Props, _ children: Children) -> AnyNode {
|
||||
return AnyNode(
|
||||
props: AnyEquatable(props),
|
||||
children: AnyEquatable(children),
|
||||
type: .host(self),
|
||||
ref: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension CompositeComponent {
|
||||
public static func node(
|
||||
_ props: Props,
|
||||
_ children: Children
|
||||
) -> AnyNode {
|
||||
return AnyNode(
|
||||
props: AnyEquatable(props),
|
||||
children: AnyEquatable(children),
|
||||
type: .composite(self),
|
||||
ref: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension RefComponent {
|
||||
public static func node(
|
||||
_ props: Props,
|
||||
_ children: Children,
|
||||
ref: Ref<RefTarget?>
|
||||
) -> AnyNode {
|
||||
return AnyNode(
|
||||
props: AnyEquatable(props),
|
||||
children: AnyEquatable(children),
|
||||
type: .host(self),
|
||||
ref: ref
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension RefComponent where Children == [AnyNode] {
|
||||
public static func node(
|
||||
_ props: Props,
|
||||
_ child: AnyNode,
|
||||
ref: Ref<RefTarget?>
|
||||
) -> AnyNode {
|
||||
return node(props, [child], ref: ref)
|
||||
}
|
||||
|
||||
public static func node(_ props: Props, ref: Ref<RefTarget?>) -> AnyNode {
|
||||
return node(props, [], ref: ref)
|
||||
}
|
||||
}
|
||||
|
||||
extension RefComponent where Children == Null {
|
||||
public static func node(_ props: Props, ref: Ref<RefTarget?>) -> AnyNode {
|
||||
return node(props, Null(), ref: ref)
|
||||
}
|
||||
}
|
||||
|
||||
extension RefComponent where Props: Default, Props.DefaultValue == Props {
|
||||
public static func node(
|
||||
_ children: Children,
|
||||
ref: Ref<RefTarget?>
|
||||
) -> AnyNode {
|
||||
return node(Props.defaultValue, children, ref: ref)
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ typealias Updater<T> = (inout T) -> ()
|
|||
|
||||
public var projectedValue: Binding<Value> { self }
|
||||
|
||||
init(get: @escaping () -> Value, set: @escaping (Value) -> ()) {
|
||||
public init(get: @escaping () -> Value, set: @escaping (Value) -> ()) {
|
||||
self.get = get
|
||||
self.set = set
|
||||
}
|
||||
|
@ -39,13 +39,6 @@ typealias Updater<T> = (inout T) -> ()
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: absent in the reference interface
|
||||
extension Binding: Equatable where Value: Equatable {
|
||||
public static func ==(lhs: Binding<Value>, rhs: Binding<Value>) -> Bool {
|
||||
lhs.wrappedValue == rhs.wrappedValue
|
||||
}
|
||||
}
|
||||
|
||||
extension Hooks {
|
||||
/** Allows a component to have its own state and to be updated when the state
|
||||
changes. Returns a very simple state container, which on initial call of
|
|
@ -0,0 +1,5 @@
|
|||
//
|
||||
// Created by Max Desiatov on 08/04/2020.
|
||||
//
|
||||
|
||||
public typealias CGFloat = Double
|
|
@ -1,151 +0,0 @@
|
|||
//
|
||||
// Component.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 07/10/2018.
|
||||
//
|
||||
|
||||
/// Type-erased version of `HostComponent` to work around [PAT restrictions](
|
||||
/// http://www.russbishop.net/swift-associated-types). Users
|
||||
/// of Tokamak shouldn't ever need to conform to this protocol directly, use
|
||||
/// `HostComponent` instead.
|
||||
public protocol AnyHostComponent {}
|
||||
|
||||
public protocol HostComponent: AnyHostComponent, Component {}
|
||||
|
||||
public protocol AnyRefComponent {
|
||||
static func update(ref: AnyObject, with value: Any)
|
||||
}
|
||||
|
||||
public protocol RefComponent: AnyRefComponent, HostComponent {
|
||||
associatedtype RefTarget
|
||||
}
|
||||
|
||||
extension AnyRefComponent where Self: RefComponent {
|
||||
public static func update(ref: AnyObject, with value: Any) {
|
||||
guard let ref = ref as? Ref<RefTarget?>,
|
||||
let value = value as? RefTarget else {
|
||||
assertionFailure("failed to cast objects passed to \(#function)")
|
||||
return
|
||||
}
|
||||
|
||||
ref.value = value
|
||||
}
|
||||
}
|
||||
|
||||
/// Type-erased version of `CompositeComponent` to work around
|
||||
/// [PAT restrictions](http://www.russbishop.net/swift-associated-types). Users
|
||||
/// of Tokamak shouldn't ever need to conform to this protocol directly, use
|
||||
/// `CompositeComponent` instead.
|
||||
public protocol AnyCompositeComponent {
|
||||
static func render(
|
||||
props: AnyEquatable,
|
||||
children: AnyEquatable,
|
||||
hooks: Hooks
|
||||
) -> AnyNode
|
||||
}
|
||||
|
||||
public protocol Component {
|
||||
associatedtype Props: Equatable
|
||||
associatedtype Children: Equatable
|
||||
|
||||
static func node(
|
||||
_ props: Props,
|
||||
_ children: Children
|
||||
) -> AnyNode
|
||||
}
|
||||
|
||||
public protocol CompositeComponent: AnyCompositeComponent, Component {
|
||||
static func render(
|
||||
props: Props,
|
||||
children: Children,
|
||||
hooks: Hooks
|
||||
) -> AnyNode
|
||||
}
|
||||
|
||||
public extension CompositeComponent {
|
||||
/// Default implementation of `AnyCompositeComponent` that delegates to
|
||||
/// `render` requirement in `CompositeComponent` PAT.
|
||||
static func render(
|
||||
props: AnyEquatable,
|
||||
children: AnyEquatable,
|
||||
hooks: Hooks
|
||||
) -> AnyNode {
|
||||
guard let props = props.value as? Props,
|
||||
let children = children.value as? Children else {
|
||||
fatalError(
|
||||
"""
|
||||
invalid types of `props` and `children` arguments passed to \(#function)
|
||||
"""
|
||||
)
|
||||
}
|
||||
return render(props: props, children: children, hooks: hooks)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol PureComponent: CompositeComponent {
|
||||
static func render(props: Props, children: Children) -> AnyNode
|
||||
}
|
||||
|
||||
public extension PureComponent {
|
||||
static func render(
|
||||
props: Props,
|
||||
children: Children,
|
||||
hooks: Hooks
|
||||
) -> AnyNode {
|
||||
return render(props: props, children: children)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol LeafComponent: CompositeComponent where Children == Null {
|
||||
static func render(props: Props, hooks: Hooks) -> AnyNode
|
||||
}
|
||||
|
||||
public extension LeafComponent {
|
||||
static func render(
|
||||
props: Props,
|
||||
children _: Children,
|
||||
hooks: Hooks
|
||||
) -> AnyNode {
|
||||
return render(props: props, hooks: hooks)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol PureLeafComponent: LeafComponent {
|
||||
static func render(props: Props) -> AnyNode
|
||||
}
|
||||
|
||||
public extension PureLeafComponent {
|
||||
static func render(props: Props, hooks: Hooks) -> AnyNode {
|
||||
return render(props: props)
|
||||
}
|
||||
}
|
||||
|
||||
enum ComponentType: Equatable {
|
||||
static func ==(lhs: ComponentType, rhs: ComponentType) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.host(ltype), .host(rtype)):
|
||||
return ltype == rtype
|
||||
case let (.composite(ltype), .composite(rtype)):
|
||||
return ltype == rtype
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
case host(AnyHostComponent.Type)
|
||||
case composite(AnyCompositeComponent.Type)
|
||||
case null
|
||||
|
||||
var composite: AnyCompositeComponent.Type? {
|
||||
guard case let .composite(type) = self else { return nil }
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
var host: AnyHostComponent.Type? {
|
||||
guard case let .host(type) = self else { return nil }
|
||||
|
||||
return type
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// Alert.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 31/12/2018.
|
||||
//
|
||||
|
||||
struct Alert: HostComponent {
|
||||
struct Props: Equatable {
|
||||
let title: String?
|
||||
let message: String?
|
||||
}
|
||||
|
||||
typealias Children = [AnyNode]
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
//
|
||||
// Animated.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/01/2019.
|
||||
//
|
||||
|
||||
protocol Animatable: Component {}
|
||||
|
||||
struct Animated<T: Component, U: Equatable>: HostComponent {
|
||||
public struct Props: Equatable {
|
||||
public struct AnimationCurve {}
|
||||
|
||||
public let duration: Second
|
||||
public let initial: T.Props
|
||||
public let isRunning: Bool
|
||||
public let isReversed: Bool
|
||||
public let keyPath: KeyPath<T.Props, U>
|
||||
public let target: U
|
||||
}
|
||||
|
||||
public typealias Children = T.Children
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
//
|
||||
// Button.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/12/2018.
|
||||
//
|
||||
|
||||
public struct Button: HostComponent {
|
||||
public struct Props: Equatable, ControlProps, StyleProps, Default {
|
||||
public static var defaultValue: Props {
|
||||
return Props()
|
||||
}
|
||||
|
||||
public let handlers: [Event: Handler<()>]
|
||||
public let style: Style?
|
||||
public let titleColor: Color?
|
||||
public let isEnabled: Bool
|
||||
public let text: String
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
handlers: [Event: Handler<()>] = [:],
|
||||
isEnabled: Bool = true,
|
||||
onPress: Handler<()>? = nil,
|
||||
text: String = "",
|
||||
titleColor: Color? = nil
|
||||
) {
|
||||
var handlers = handlers
|
||||
if let onPress = onPress {
|
||||
handlers[.touchUpInside] = onPress
|
||||
}
|
||||
self.handlers = handlers
|
||||
self.style = style
|
||||
self.titleColor = titleColor
|
||||
self.isEnabled = isEnabled
|
||||
self.text = text
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = [AnyNode]
|
||||
}
|
||||
|
||||
extension Button.Props: ExpressibleByStringLiteral {
|
||||
public init(stringLiteral value: String) {
|
||||
self.init(text: value)
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
//
|
||||
// CollectionView.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 3/6/19.
|
||||
//
|
||||
|
||||
public struct CollectionView<T: CellProvider>: HostComponent {
|
||||
public struct Props: Equatable, StyleProps {
|
||||
public let cellProps: T.Props
|
||||
public let sections: T.Sections
|
||||
public let onSelect: Handler<CellPath>?
|
||||
public let style: Style?
|
||||
public let scrollOptions: ScrollOptions?
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
cellProps: T.Props,
|
||||
onSelect: Handler<CellPath>? = nil,
|
||||
scrollOptions: ScrollOptions? = nil,
|
||||
singleSection: T.Sections.Element
|
||||
) {
|
||||
self.cellProps = cellProps
|
||||
sections = T.Sections.single(section: singleSection)
|
||||
self.onSelect = onSelect
|
||||
self.style = style
|
||||
self.scrollOptions = scrollOptions
|
||||
}
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
cellProps: T.Props,
|
||||
sections: T.Sections,
|
||||
onSelect: Handler<CellPath>? = nil,
|
||||
scrollOptions: ScrollOptions? = nil
|
||||
) {
|
||||
self.cellProps = cellProps
|
||||
self.sections = sections
|
||||
self.onSelect = onSelect
|
||||
self.style = style
|
||||
self.scrollOptions = scrollOptions
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = Null
|
||||
}
|
||||
|
||||
extension CollectionView.Props where T.Props == Null {
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
sections: T.Sections,
|
||||
onSelect: Handler<CellPath>? = nil,
|
||||
scrollOptions: ScrollOptions? = nil
|
||||
) {
|
||||
cellProps = Null()
|
||||
self.sections = sections
|
||||
self.onSelect = onSelect
|
||||
self.style = style
|
||||
self.scrollOptions = scrollOptions
|
||||
}
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
onSelect: Handler<CellPath>? = nil,
|
||||
singleSection: T.Sections.Element,
|
||||
scrollOptions: ScrollOptions? = nil
|
||||
) {
|
||||
cellProps = Null()
|
||||
sections = T.Sections.single(section: singleSection)
|
||||
self.onSelect = onSelect
|
||||
self.style = style
|
||||
self.scrollOptions = scrollOptions
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// DatePicker.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 2/4/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct DatePicker: HostComponent {
|
||||
public struct Props: Equatable, ControlProps, StyleProps,
|
||||
ValueControlProps {
|
||||
public let handlers: EventHandlers
|
||||
public let style: Style?
|
||||
public let value: Date
|
||||
public let valueHandler: Handler<Date>?
|
||||
public let isEnabled: Bool
|
||||
public let isAnimated: Bool
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
handlers: EventHandlers = [:],
|
||||
isAnimated: Bool = true,
|
||||
isEnabled: Bool = true,
|
||||
value: Date,
|
||||
valueHandler: Handler<Date>? = nil
|
||||
) {
|
||||
self.handlers = handlers
|
||||
self.style = style
|
||||
self.value = value
|
||||
self.valueHandler = valueHandler
|
||||
self.isEnabled = isEnabled
|
||||
self.isAnimated = isAnimated
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = Null
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
//
|
||||
// ImageView.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 2/17/19.
|
||||
//
|
||||
|
||||
public struct ImageView: HostComponent {
|
||||
public typealias Children = [AnyNode]
|
||||
|
||||
public struct Props: Equatable, StyleProps {
|
||||
public let image: Image
|
||||
public let style: Style?
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
image: Image
|
||||
) {
|
||||
self.image = image
|
||||
self.style = style
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// Label.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/12/2018.
|
||||
//
|
||||
|
||||
public struct Label: HostComponent {
|
||||
public struct Props: Equatable, StyleProps, Default {
|
||||
public static var defaultValue: Props {
|
||||
return Props()
|
||||
}
|
||||
|
||||
public let alignment: TextAlignment
|
||||
public let lineBreakMode: LineBreakMode
|
||||
public let numberOfLines: Int
|
||||
public let style: Style?
|
||||
public let text: String
|
||||
public let textColor: Color?
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
alignment: TextAlignment = .natural,
|
||||
lineBreakMode: LineBreakMode = .truncateTail,
|
||||
numberOfLines: Int = 1,
|
||||
text: String = "",
|
||||
textColor: Color? = nil
|
||||
) {
|
||||
self.alignment = alignment
|
||||
self.lineBreakMode = lineBreakMode
|
||||
self.numberOfLines = numberOfLines
|
||||
self.text = text
|
||||
self.textColor = textColor
|
||||
self.style = style
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = [AnyNode]
|
||||
}
|
||||
|
||||
extension Label.Props: ExpressibleByStringLiteral {
|
||||
public init(stringLiteral value: String) {
|
||||
self.init(text: value)
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
//
|
||||
// ListView.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 21/01/2019.
|
||||
//
|
||||
|
||||
public struct CellPath {
|
||||
public let section: Int
|
||||
public let item: Int
|
||||
|
||||
public init(section: Int, item: Int) {
|
||||
self.section = section
|
||||
self.item = item
|
||||
}
|
||||
}
|
||||
|
||||
/** A collection of sections with random access to its elements and
|
||||
an ability to create a new `SectionedModel` with a single section. The most
|
||||
basic `SectionedModel` is an array of arrays `[[T]]`, where every element
|
||||
in the top level array is a section `[T]` and every element `T` is a row/item
|
||||
in this section.
|
||||
*/
|
||||
public protocol SectionedModel: RandomAccessCollection
|
||||
where Element: RandomAccessCollection, Index == Int, Element.Index == Int {
|
||||
static func single(section: Element) -> Self
|
||||
}
|
||||
|
||||
extension Array: SectionedModel
|
||||
where Element: RandomAccessCollection, Element.Index == Int {
|
||||
public static func single(section: Element) -> [Element] {
|
||||
return [section]
|
||||
}
|
||||
}
|
||||
|
||||
public protocol IdentifiedCellProvider {
|
||||
associatedtype Props: Equatable
|
||||
associatedtype Identifier: RawRepresentable, CaseIterable
|
||||
where Identifier.RawValue == String
|
||||
associatedtype Model: Equatable
|
||||
associatedtype Sections: SectionedModel & Equatable = [[Model]] where
|
||||
Sections.Element.Element == Model
|
||||
|
||||
static func cell(
|
||||
props: Props,
|
||||
item: Model,
|
||||
path: CellPath
|
||||
) -> (Identifier, AnyNode)
|
||||
}
|
||||
|
||||
public enum SingleIdentifier: String, CaseIterable {
|
||||
case single
|
||||
}
|
||||
|
||||
public protocol CellProvider: IdentifiedCellProvider
|
||||
where Identifier == SingleIdentifier {
|
||||
static func cell(
|
||||
props: Props,
|
||||
item: Model,
|
||||
path: CellPath
|
||||
) -> AnyNode
|
||||
}
|
||||
|
||||
extension CellProvider {
|
||||
public static func cell(
|
||||
props: Props,
|
||||
item: Model,
|
||||
path: CellPath
|
||||
) -> (Identifier, AnyNode) {
|
||||
return (.single, cell(props: props, item: item, path: path))
|
||||
}
|
||||
}
|
||||
|
||||
public struct ListView<T: CellProvider>: HostComponent {
|
||||
public struct Props: Equatable, StyleProps {
|
||||
public let cellProps: T.Props
|
||||
public let model: T.Sections
|
||||
public let onSelect: Handler<CellPath>?
|
||||
public let style: Style?
|
||||
public let scrollOptions: ScrollOptions?
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
cellProps: T.Props,
|
||||
onSelect: Handler<CellPath>? = nil,
|
||||
scrollOptions: ScrollOptions? = nil,
|
||||
singleSection: T.Sections.Element
|
||||
) {
|
||||
self.cellProps = cellProps
|
||||
model = T.Sections.single(section: singleSection)
|
||||
self.onSelect = onSelect
|
||||
self.style = style
|
||||
self.scrollOptions = scrollOptions
|
||||
}
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
cellProps: T.Props,
|
||||
model: T.Sections,
|
||||
onSelect: Handler<CellPath>? = nil,
|
||||
scrollOptions: ScrollOptions? = nil
|
||||
) {
|
||||
self.cellProps = cellProps
|
||||
self.model = model
|
||||
self.onSelect = onSelect
|
||||
self.style = style
|
||||
self.scrollOptions = scrollOptions
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = Null
|
||||
}
|
||||
|
||||
extension ListView.Props where T.Props == Null {
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
model: T.Sections,
|
||||
onSelect: Handler<CellPath>? = nil,
|
||||
scrollOptions: ScrollOptions? = nil
|
||||
) {
|
||||
cellProps = Null()
|
||||
self.model = model
|
||||
self.onSelect = onSelect
|
||||
self.style = style
|
||||
self.scrollOptions = scrollOptions
|
||||
}
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
onSelect: Handler<CellPath>? = nil,
|
||||
scrollOptions: ScrollOptions? = nil,
|
||||
singleSection: T.Sections.Element
|
||||
) {
|
||||
cellProps = Null()
|
||||
model = T.Sections.single(section: singleSection)
|
||||
self.onSelect = onSelect
|
||||
self.style = style
|
||||
self.scrollOptions = scrollOptions
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// NavigationController.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 06/01/2019.
|
||||
//
|
||||
|
||||
public struct NavigationController: HostComponent {
|
||||
public typealias Children = [AnyNode]
|
||||
|
||||
public struct Props: Equatable {
|
||||
public let hidesBarsWhenKeyboardAppears: Bool?
|
||||
public let popAnimated: Bool
|
||||
public let prefersLargeTitles: Bool
|
||||
public let pushAnimated: Bool
|
||||
public let onPop: Handler<()>
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
//
|
||||
// ScrollView.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 2/28/19.
|
||||
//
|
||||
|
||||
public struct ScrollView: HostComponent {
|
||||
public struct Props: Equatable, StyleProps, Default {
|
||||
public static var defaultValue: Props {
|
||||
return Props()
|
||||
}
|
||||
|
||||
public let style: Style?
|
||||
public let scrollOptions: ScrollOptions
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
alwaysBounceHorizontal: Bool = false,
|
||||
alwaysBounceVertical: Bool = false,
|
||||
bounces: Bool = true,
|
||||
bouncesZoom: Bool = true,
|
||||
contentInset: ScrollOptions.EdgeInsets = ScrollOptions.EdgeInsets(),
|
||||
indicatorStyle: ScrollOptions.IndicatorStyle = .default,
|
||||
isDirectionalLockEnabled: Bool = false,
|
||||
isPagingEnabled: Bool = false,
|
||||
isScrollEnabled: Bool = true,
|
||||
maximumZoomScale: Float = 1.0,
|
||||
minimumZoomScale: Float = 1.0,
|
||||
scrollIndicatorInsets: ScrollOptions.EdgeInsets = ScrollOptions.EdgeInsets(),
|
||||
scrollsToTop: Bool = true,
|
||||
showsHorizontalScrollIndicator: Bool = true,
|
||||
showsVerticalScrollIndicator: Bool = true,
|
||||
zoomScale: Float = 1.0
|
||||
) {
|
||||
self.style = style
|
||||
scrollOptions = ScrollOptions(
|
||||
alwaysBounceHorizontal: alwaysBounceHorizontal,
|
||||
alwaysBounceVertical: alwaysBounceVertical,
|
||||
bounces: bounces,
|
||||
bouncesZoom: bouncesZoom,
|
||||
contentInset: contentInset,
|
||||
indicatorStyle: indicatorStyle,
|
||||
isDirectionalLockEnabled: isDirectionalLockEnabled,
|
||||
isPagingEnabled: isPagingEnabled,
|
||||
isScrollEnabled: isScrollEnabled,
|
||||
maximumZoomScale: maximumZoomScale,
|
||||
minimumZoomScale: minimumZoomScale,
|
||||
scrollIndicatorInsets: scrollIndicatorInsets,
|
||||
scrollsToTop: scrollsToTop,
|
||||
showsHorizontalScrollIndicator: showsHorizontalScrollIndicator,
|
||||
showsVerticalScrollIndicator: showsVerticalScrollIndicator,
|
||||
zoomScale: zoomScale
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = AnyNode
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// SegmentedControl.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 05/01/2019.
|
||||
//
|
||||
|
||||
public struct SegmentedControl: HostComponent {
|
||||
public struct Props: Equatable, ControlProps, StyleProps,
|
||||
ValueControlProps {
|
||||
public let handlers: EventHandlers
|
||||
public let style: Style?
|
||||
public let value: Int
|
||||
public let valueHandler: Handler<Int>?
|
||||
public let isEnabled: Bool
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
handlers: EventHandlers = [:],
|
||||
isEnabled: Bool = true,
|
||||
value: Int,
|
||||
valueHandler: Handler<Int>? = nil
|
||||
) {
|
||||
self.handlers = handlers
|
||||
self.style = style
|
||||
self.value = value
|
||||
self.valueHandler = valueHandler
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = [String]
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// Slider.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 29/12/2018.
|
||||
//
|
||||
|
||||
public struct Slider: HostComponent {
|
||||
public struct Props: Equatable, ControlProps, StyleProps,
|
||||
ValueControlProps {
|
||||
public let handlers: EventHandlers
|
||||
public let style: Style?
|
||||
public let value: Float
|
||||
public let valueHandler: Handler<Float>?
|
||||
public let isEnabled: Bool
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
handlers: EventHandlers = [:],
|
||||
isEnabled: Bool = true,
|
||||
value: Float,
|
||||
valueHandler: Handler<Float>? = nil
|
||||
) {
|
||||
self.handlers = handlers
|
||||
self.style = style
|
||||
self.value = value
|
||||
self.valueHandler = valueHandler
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = Null
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
//
|
||||
// StackView.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/12/2018.
|
||||
//
|
||||
|
||||
public struct StackView: HostComponent {
|
||||
public struct Props: Equatable, StyleProps, Default {
|
||||
public static var defaultValue: Props {
|
||||
return Props()
|
||||
}
|
||||
|
||||
public enum Alignment {
|
||||
case fill
|
||||
case center
|
||||
case leading
|
||||
case trailing
|
||||
case top
|
||||
case bottom
|
||||
}
|
||||
|
||||
public enum Axis {
|
||||
case horizontal
|
||||
case vertical
|
||||
}
|
||||
|
||||
public enum Distribution {
|
||||
case fill
|
||||
case fillEqually
|
||||
case fillProportionally
|
||||
case equalSpacing
|
||||
}
|
||||
|
||||
public let alignment: Alignment
|
||||
public let axis: Axis
|
||||
public let distribution: Distribution
|
||||
public let spacing: Double
|
||||
|
||||
/// not exposing style: UIStackView is a non-rendering subclass of UIView
|
||||
/// https://useyourloaf.com/blog/stack-view-background-color/
|
||||
public let style: Style?
|
||||
|
||||
public init(
|
||||
_ frame: Rectangle,
|
||||
alignment: Alignment = .fill,
|
||||
axis: Axis = .horizontal,
|
||||
distribution: Distribution = .fill,
|
||||
spacing: Double = 0
|
||||
) {
|
||||
self.alignment = alignment
|
||||
self.axis = axis
|
||||
self.distribution = distribution
|
||||
style = Style(frame)
|
||||
self.spacing = spacing
|
||||
}
|
||||
|
||||
public init(
|
||||
_ constraint: Constraint,
|
||||
alignment: Alignment = .fill,
|
||||
axis: Axis = .horizontal,
|
||||
distribution: Distribution = .fill,
|
||||
spacing: Double = 0
|
||||
) {
|
||||
self.alignment = alignment
|
||||
self.axis = axis
|
||||
self.distribution = distribution
|
||||
style = Style(constraint)
|
||||
self.spacing = spacing
|
||||
}
|
||||
|
||||
public init(
|
||||
_ constraints: [Constraint],
|
||||
alignment: Alignment = .fill,
|
||||
axis: Axis = .horizontal,
|
||||
distribution: Distribution = .fill,
|
||||
spacing: Double = 0
|
||||
) {
|
||||
self.alignment = alignment
|
||||
self.axis = axis
|
||||
self.distribution = distribution
|
||||
style = Style(constraints)
|
||||
self.spacing = spacing
|
||||
}
|
||||
|
||||
public init(
|
||||
alignment: Alignment = .fill,
|
||||
axis: Axis = .horizontal,
|
||||
distribution: Distribution = .fill,
|
||||
spacing: Double = 0
|
||||
) {
|
||||
self.alignment = alignment
|
||||
self.axis = axis
|
||||
self.distribution = distribution
|
||||
style = nil
|
||||
self.spacing = spacing
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = [AnyNode]
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
//
|
||||
// Stepper.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 1/21/19.
|
||||
//
|
||||
|
||||
public struct Stepper: HostComponent {
|
||||
public struct Props: Equatable, ControlProps, StyleProps,
|
||||
ValueControlProps {
|
||||
public let autorepeat: Bool
|
||||
public let handlers: EventHandlers
|
||||
public let isEnabled: Bool
|
||||
public let maximumValue: Double
|
||||
public let minimumValue: Double
|
||||
public let stepValue: Double
|
||||
public let value: Double
|
||||
public let valueHandler: Handler<Double>?
|
||||
public let wraps: Bool
|
||||
public let style: Style?
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
autorepeat: Bool = true,
|
||||
handlers: EventHandlers = [:],
|
||||
isEnabled: Bool = true,
|
||||
maximumValue: Double = 100.0,
|
||||
minimumValue: Double = 0.0,
|
||||
stepValue: Double = 1.0,
|
||||
value: Double,
|
||||
valueHandler: Handler<Double>? = nil,
|
||||
wraps: Bool = false
|
||||
) {
|
||||
self.autorepeat = autorepeat
|
||||
self.handlers = handlers
|
||||
self.isEnabled = isEnabled
|
||||
self.maximumValue = maximumValue
|
||||
self.minimumValue = minimumValue
|
||||
self.stepValue = stepValue
|
||||
self.value = value
|
||||
self.valueHandler = valueHandler
|
||||
self.wraps = wraps
|
||||
self.style = style
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = Null
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
//
|
||||
// Switch.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 1/21/19.
|
||||
//
|
||||
|
||||
public struct Switch: HostComponent {
|
||||
public struct Props: Equatable, ControlProps, StyleProps,
|
||||
ValueControlProps {
|
||||
public let handlers: EventHandlers
|
||||
public let style: Style?
|
||||
public let value: Bool
|
||||
public let valueHandler: Handler<Bool>?
|
||||
public let isEnabled: Bool
|
||||
public let isAnimated: Bool
|
||||
public let onImage: Image?
|
||||
public let offImage: Image?
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
handlers: EventHandlers = [:],
|
||||
isAnimated: Bool = true,
|
||||
isEnabled: Bool = true,
|
||||
offImage: Image? = nil,
|
||||
onImage: Image? = nil,
|
||||
value: Bool,
|
||||
valueHandler: Handler<Bool>? = nil
|
||||
) {
|
||||
self.handlers = handlers
|
||||
self.style = style
|
||||
self.value = value
|
||||
self.valueHandler = valueHandler
|
||||
self.isEnabled = isEnabled
|
||||
self.isAnimated = isAnimated
|
||||
self.offImage = offImage
|
||||
self.onImage = onImage
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = Null
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
//
|
||||
// TextField.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 2/22/19.
|
||||
//
|
||||
|
||||
public struct TextField: HostComponent {
|
||||
public struct Props: Equatable, ControlProps, StyleProps,
|
||||
ValueControlProps {
|
||||
public enum ViewMode {
|
||||
case never
|
||||
case whileEditing
|
||||
case unlessEditing
|
||||
case always
|
||||
}
|
||||
|
||||
public enum TextAutocapitalizationType {
|
||||
case none
|
||||
case words
|
||||
case sentences
|
||||
case allCharacters
|
||||
}
|
||||
|
||||
public enum TextAutocorrectionType {
|
||||
case `default`
|
||||
case no
|
||||
case yes
|
||||
}
|
||||
|
||||
public enum TextSpellCheckingType {
|
||||
case `default`
|
||||
case no
|
||||
case yes
|
||||
}
|
||||
|
||||
public enum KeyboardType {
|
||||
case `default`
|
||||
case asciiCapable
|
||||
case numbersAndPunctuation
|
||||
case URL
|
||||
case numberPad
|
||||
case phonePad
|
||||
case namePhonePad
|
||||
case emailAddress
|
||||
case decimalPad
|
||||
case twitter
|
||||
case webSearch
|
||||
case asciiCapableNumberPad
|
||||
}
|
||||
|
||||
public enum KeyboardAppearance {
|
||||
case `default`
|
||||
case dark
|
||||
case light
|
||||
}
|
||||
|
||||
public enum ReturnKeyType {
|
||||
case `default`
|
||||
case go
|
||||
case google
|
||||
case join
|
||||
case next
|
||||
case route
|
||||
case search
|
||||
case send
|
||||
case yahoo
|
||||
case done
|
||||
case emergencyCall
|
||||
case `continue`
|
||||
}
|
||||
|
||||
public enum BorderStyle {
|
||||
case none
|
||||
case line
|
||||
case bezel
|
||||
case roundedRect
|
||||
}
|
||||
|
||||
public let autocapitalizationType: TextAutocapitalizationType
|
||||
public let autocorrectionType: TextAutocorrectionType
|
||||
public let borderStyle: BorderStyle
|
||||
public let clearButtonMode: ViewMode
|
||||
public let clearsOnBeginEditing: Bool
|
||||
public let clearsOnInsertion: Bool
|
||||
public let handlers: EventHandlers
|
||||
public let style: Style?
|
||||
public let value: String
|
||||
public let valueHandler: Handler<String>?
|
||||
public let isEnabled: Bool
|
||||
public let isSecureTextEntry: Bool
|
||||
public let keyboardAppearance: KeyboardAppearance
|
||||
public let keyboardType: KeyboardType
|
||||
public let placeholder: String?
|
||||
public let returnKeyType: ReturnKeyType
|
||||
public let spellCheckingType: TextSpellCheckingType
|
||||
public let textAlignment: TextAlignment
|
||||
public let textColor: Color
|
||||
public let isAnimated: Bool
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
autocapitalizationType: TextAutocapitalizationType = .sentences,
|
||||
autocorrectionType: TextAutocorrectionType = .default,
|
||||
borderStyle: BorderStyle = .bezel,
|
||||
clearButtonMode: ViewMode = .never,
|
||||
clearsOnBeginEditing: Bool = false,
|
||||
clearsOnInsertion: Bool = false,
|
||||
handlers: EventHandlers = [:],
|
||||
isAnimated: Bool = true,
|
||||
isEnabled: Bool = true,
|
||||
isSecureTextEntry: Bool = false,
|
||||
keyboardAppearance: KeyboardAppearance = .default,
|
||||
keyboardType: KeyboardType = .default,
|
||||
placeholder: String? = nil,
|
||||
returnKeyType: ReturnKeyType = .default,
|
||||
spellCheckingType: TextSpellCheckingType = .default,
|
||||
textAlignment: TextAlignment = .natural,
|
||||
textColor: Color = .black,
|
||||
value: String,
|
||||
valueHandler: Handler<String>? = nil
|
||||
) {
|
||||
self.autocapitalizationType = autocapitalizationType
|
||||
self.autocorrectionType = autocorrectionType
|
||||
self.borderStyle = borderStyle
|
||||
self.clearButtonMode = clearButtonMode
|
||||
self.clearsOnBeginEditing = clearsOnBeginEditing
|
||||
self.clearsOnInsertion = clearsOnInsertion
|
||||
self.handlers = handlers
|
||||
self.style = style
|
||||
self.value = value
|
||||
self.valueHandler = valueHandler
|
||||
self.isSecureTextEntry = isSecureTextEntry
|
||||
self.isEnabled = isEnabled
|
||||
self.keyboardAppearance = keyboardAppearance
|
||||
self.keyboardType = keyboardType
|
||||
self.placeholder = placeholder
|
||||
self.returnKeyType = returnKeyType
|
||||
self.spellCheckingType = spellCheckingType
|
||||
self.textAlignment = textAlignment
|
||||
self.textColor = textColor
|
||||
self.isAnimated = isAnimated
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = Null
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
//
|
||||
// TextView.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 3/27/19.
|
||||
//
|
||||
|
||||
public struct TextView: HostComponent {
|
||||
public struct Props: Equatable, StyleProps, ValueControlProps {
|
||||
public let style: Style?
|
||||
public let allowsEditingTextAttributes: Bool
|
||||
public let isEditable: Bool
|
||||
public let textAlignment: TextAlignment
|
||||
public let textColor: Color?
|
||||
public let scrollOptions: ScrollOptions?
|
||||
public let value: String
|
||||
public let valueHandler: Handler<String>?
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
allowsEditingTextAttributes: Bool = false,
|
||||
isEditable: Bool = true,
|
||||
scrollOptions: ScrollOptions? = nil,
|
||||
textAlignment: TextAlignment = .natural,
|
||||
textColor: Color? = nil,
|
||||
value: String = "",
|
||||
valueHandler: Handler<String>? = nil
|
||||
) {
|
||||
self.style = style
|
||||
self.allowsEditingTextAttributes = allowsEditingTextAttributes
|
||||
self.isEditable = isEditable
|
||||
self.textAlignment = textAlignment
|
||||
self.textColor = textColor
|
||||
self.scrollOptions = scrollOptions
|
||||
self.value = value
|
||||
self.valueHandler = valueHandler
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = Null
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// Throbber.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 20/03/2019.
|
||||
//
|
||||
|
||||
public struct Throbber: HostComponent {
|
||||
public struct Props: Equatable, StyleProps {
|
||||
public enum Variety {
|
||||
case whiteLarge
|
||||
case white
|
||||
case gray
|
||||
}
|
||||
|
||||
/// Value of this property overrides the value of `variety`
|
||||
public let color: Color?
|
||||
public let isAnimating: Bool
|
||||
public let hidesWhenStopped: Bool
|
||||
public let style: Style?
|
||||
public let variety: Variety
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
color: Color? = nil,
|
||||
isAnimating: Bool = false,
|
||||
hidesWhenStopped: Bool = true,
|
||||
variety: Variety = .gray
|
||||
) {
|
||||
self.style = style
|
||||
self.color = color
|
||||
self.isAnimating = isAnimating
|
||||
self.hidesWhenStopped = hidesWhenStopped
|
||||
self.variety = variety
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = Null
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
//
|
||||
// View.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 29/12/2018.
|
||||
//
|
||||
|
||||
public struct ViewComponent: HostComponent {
|
||||
public typealias Children = [AnyNode]
|
||||
|
||||
public struct Props: Equatable, StyleProps, Default {
|
||||
public static var defaultValue: Props {
|
||||
return Props()
|
||||
}
|
||||
|
||||
public let style: Style?
|
||||
|
||||
public init(_ style: Style? = nil) {
|
||||
self.style = style
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
//
|
||||
// ModalPresenter.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 31/12/2018.
|
||||
//
|
||||
|
||||
public struct ModalPresenter: HostComponent {
|
||||
public struct Props: Equatable, Default {
|
||||
public static var defaultValue: Props {
|
||||
return Props()
|
||||
}
|
||||
|
||||
public enum PresentationStyle {
|
||||
/** Renderer targets up in the tree of nodes owning the presenter node are
|
||||
removed after the presentation completes.
|
||||
*/
|
||||
case fullScreen
|
||||
case pageSheet
|
||||
case formSheet
|
||||
|
||||
/** Renderer targets up in the tree of nodes owning the presenter node are
|
||||
removed after the presentation completes.
|
||||
*/
|
||||
case currentContext
|
||||
|
||||
/** Renderer targets up in the tree of nodes owning the presenter node are
|
||||
not removed from the target hierarchy when the presentation finishes.
|
||||
So if the presented component does not fill the screen with opaque
|
||||
content, the underlying content shows through.
|
||||
*/
|
||||
case overCurrentContext
|
||||
|
||||
/** Renderer targets up in the tree of nodes owning the presenter node are
|
||||
not removed from the target hierarchy when the presentation finishes.
|
||||
So if the presented component does not fill the screen with opaque
|
||||
content, the underlying content shows through.
|
||||
*/
|
||||
case overFullScreen
|
||||
|
||||
case blurOverFullScreen
|
||||
|
||||
case popover
|
||||
}
|
||||
|
||||
public enum TransitionStyle {
|
||||
case coverVertical
|
||||
case flipHorizontal
|
||||
case crossDissolve
|
||||
case partialCurl
|
||||
}
|
||||
|
||||
public let dismissAnimated: Bool
|
||||
public let presentAnimated: Bool
|
||||
public let presentationStyle: PresentationStyle?
|
||||
public let transitionStyle: TransitionStyle?
|
||||
|
||||
public init(
|
||||
dismissAnimated: Bool = true,
|
||||
presentAnimated: Bool = true,
|
||||
presentationStyle: PresentationStyle? = nil,
|
||||
transitionStyle: TransitionStyle? = nil
|
||||
) {
|
||||
self.dismissAnimated = dismissAnimated
|
||||
self.presentAnimated = presentAnimated
|
||||
self.presentationStyle = presentationStyle
|
||||
self.transitionStyle = transitionStyle
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = AnyNode
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
//
|
||||
// NavigationItem.swift
|
||||
// TokamakUIKit
|
||||
//
|
||||
// Created by Max Desiatov on 04/02/2019.
|
||||
//
|
||||
|
||||
public struct NavigationItem: HostComponent {
|
||||
public struct Props: Equatable {
|
||||
/// The mode to use when displaying the title of the navigation bar.
|
||||
public enum TitleMode {
|
||||
case automatic
|
||||
case large
|
||||
case standard
|
||||
}
|
||||
|
||||
public let title: String?
|
||||
public let titleMode: TitleMode
|
||||
|
||||
public init(title: String? = nil, titleMode: TitleMode = .automatic) {
|
||||
self.title = title
|
||||
self.titleMode = titleMode
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = AnyNode
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
//
|
||||
// NavigationPresenter.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 31/12/2018.
|
||||
//
|
||||
|
||||
public protocol NavigationRouter {
|
||||
associatedtype Props: Equatable
|
||||
associatedtype Route: Equatable
|
||||
|
||||
static func route(
|
||||
props: Props,
|
||||
route: Route,
|
||||
push: @escaping (Route) -> (),
|
||||
pop: @escaping () -> (),
|
||||
hooks: Hooks
|
||||
) -> AnyNode
|
||||
}
|
||||
|
||||
public struct NavigationPresenter<T: NavigationRouter>: LeafComponent {
|
||||
public struct Props: Equatable {
|
||||
public let hidesBarsWhenKeyboardAppears: Bool?
|
||||
public let initial: T.Route
|
||||
public let popAnimated: Bool
|
||||
public let prefersLargeTitles: Bool
|
||||
public let pushAnimated: Bool
|
||||
public let routerProps: T.Props
|
||||
|
||||
public init(
|
||||
hidesBarsWhenKeyboardAppears: Bool? = nil,
|
||||
initial: T.Route,
|
||||
popAnimated: Bool = true,
|
||||
prefersLargeTitles: Bool = false,
|
||||
pushAnimated: Bool = true,
|
||||
routerProps: T.Props
|
||||
) {
|
||||
self.hidesBarsWhenKeyboardAppears = hidesBarsWhenKeyboardAppears
|
||||
self.initial = initial
|
||||
self.popAnimated = popAnimated
|
||||
self.prefersLargeTitles = prefersLargeTitles
|
||||
self.pushAnimated = pushAnimated
|
||||
self.routerProps = routerProps
|
||||
}
|
||||
}
|
||||
|
||||
public static func render(props: Props, hooks: Hooks) -> AnyNode {
|
||||
let stack = hooks.state([props.initial])
|
||||
|
||||
let pop = { () -> () in stack.wrappedValue.remove(at: stack.wrappedValue.count - 1) }
|
||||
|
||||
return NavigationController.node(
|
||||
.init(
|
||||
hidesBarsWhenKeyboardAppears: props.hidesBarsWhenKeyboardAppears,
|
||||
popAnimated: props.popAnimated,
|
||||
prefersLargeTitles: props.prefersLargeTitles,
|
||||
pushAnimated: props.pushAnimated,
|
||||
onPop: Handler(pop)
|
||||
),
|
||||
stack.wrappedValue.map {
|
||||
T.route(
|
||||
props: props.routerProps,
|
||||
route: $0,
|
||||
push: { stack.wrappedValue += [$0] },
|
||||
pop: pop,
|
||||
hooks: hooks
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension NavigationPresenter.Props where T.Props == Null {
|
||||
public init(
|
||||
hidesBarsWhenKeyboardAppears: Bool? = nil,
|
||||
initial: T.Route,
|
||||
popAnimated: Bool = true,
|
||||
prefersLargeTitles: Bool = false,
|
||||
pushAnimated: Bool = true
|
||||
) {
|
||||
self.hidesBarsWhenKeyboardAppears = hidesBarsWhenKeyboardAppears
|
||||
self.initial = initial
|
||||
self.popAnimated = popAnimated
|
||||
self.prefersLargeTitles = prefersLargeTitles
|
||||
self.pushAnimated = pushAnimated
|
||||
routerProps = Null()
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// TabItem.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 04/02/2019.
|
||||
//
|
||||
|
||||
public struct TabItem: HostComponent {
|
||||
public struct Props: Equatable {
|
||||
public let badgeColor: Color?
|
||||
public let badgeValue: String?
|
||||
public let image: Image?
|
||||
public let selectedImage: Image?
|
||||
public let title: String?
|
||||
|
||||
public init(
|
||||
badgeColor: Color? = nil,
|
||||
badgeValue: String? = nil,
|
||||
image: Image? = nil,
|
||||
selectedImage: Image? = nil,
|
||||
title: String? = nil
|
||||
) {
|
||||
self.badgeColor = badgeColor
|
||||
self.badgeValue = badgeValue
|
||||
self.image = image
|
||||
self.selectedImage = selectedImage
|
||||
self.title = title
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = AnyNode
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
//
|
||||
// TabPresenter.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 3/13/19.
|
||||
//
|
||||
|
||||
public struct TabPresenter: HostComponent {
|
||||
public typealias Children = [AnyNode]
|
||||
|
||||
public struct Props: Equatable {
|
||||
public let isAnimated: Bool
|
||||
public let selectedIndex: Binding<Int>?
|
||||
|
||||
public init(
|
||||
isAnimated: Bool = false,
|
||||
selectedIndex: Binding<Int>? = nil
|
||||
) {
|
||||
self.isAnimated = isAnimated
|
||||
self.selectedIndex = selectedIndex
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// Accessibility.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 2/21/19.
|
||||
//
|
||||
|
||||
public struct Accessibility: Equatable {
|
||||
public let identifier: String?
|
||||
public let language: String?
|
||||
public let label: String?
|
||||
public let hint: String?
|
||||
public let value: String?
|
||||
public let elementsHidden: Bool
|
||||
public let isModal: Bool
|
||||
|
||||
public init(
|
||||
elementsHidden: Bool = false,
|
||||
hint: String? = nil,
|
||||
isModal: Bool = false,
|
||||
label: String? = "",
|
||||
language: String? = nil,
|
||||
value: String? = "",
|
||||
identifier: String? = ""
|
||||
) {
|
||||
self.elementsHidden = elementsHidden
|
||||
self.hint = hint
|
||||
self.isModal = isModal
|
||||
self.label = label
|
||||
self.language = language
|
||||
self.value = value
|
||||
self.identifier = identifier
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// Bottom.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Bottom: Equatable {
|
||||
public let target: Constraint.SafeAreaTarget
|
||||
public let constant: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .bottom(Bottom(
|
||||
target: target, constant: constant
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .bottom(Bottom(
|
||||
target: .external(target), constant: constant
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// Center.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Center: Equatable {
|
||||
public let target: Constraint.Target
|
||||
public let constant: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .center(Center(target: target, constant: constant))
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// CenterX.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct CenterX: Equatable {
|
||||
public let target: Constraint.SafeAreaTarget
|
||||
public let constant: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .centerX(CenterX(
|
||||
target: target, constant: constant
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .centerX(CenterX(
|
||||
target: .external(target), constant: constant
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
//
|
||||
// CenterY.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct CenterY: Equatable {
|
||||
public let target: Constraint.SafeAreaTarget
|
||||
public let constant: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
constant: Double = 0,
|
||||
multiplier: Double = 1
|
||||
) -> Constraint {
|
||||
return .centerY(CenterY(
|
||||
target: target, constant: constant
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .centerY(CenterY(
|
||||
target: .external(target), constant: constant
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
//
|
||||
// Constraint.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 16/10/2018.
|
||||
//
|
||||
|
||||
public enum Constraint: Equatable {
|
||||
public enum SafeAreaTarget: Equatable {
|
||||
case external(Target)
|
||||
case safeArea
|
||||
}
|
||||
|
||||
public enum Target {
|
||||
case next
|
||||
case parent
|
||||
}
|
||||
|
||||
public enum OwnTarget: Equatable {
|
||||
case external(Target)
|
||||
case own
|
||||
}
|
||||
|
||||
case firstBaseline(FirstBaseline)
|
||||
case lastBaseline(LastBaseline)
|
||||
|
||||
case center(Center)
|
||||
case centerX(CenterX)
|
||||
case centerY(CenterY)
|
||||
|
||||
case width(Width)
|
||||
case height(Height)
|
||||
case size(SizeConstraint)
|
||||
|
||||
case edges(Edges)
|
||||
case leading(Leading)
|
||||
case trailing(Trailing)
|
||||
case left(Left)
|
||||
case right(Right)
|
||||
case top(Top)
|
||||
case bottom(Bottom)
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
//
|
||||
// Edges.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Edges: Equatable {
|
||||
public let target: Constraint.SafeAreaTarget
|
||||
public let insets: Insets
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
insets: Insets = .zero
|
||||
) -> Constraint {
|
||||
return .edges(Edges(target: target, insets: insets))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
insets: Insets = .zero
|
||||
) -> Constraint {
|
||||
return .edges(Edges(target: .external(target), insets: insets))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
inset: Double
|
||||
) -> Constraint {
|
||||
return .edges(Edges(target: .external(target), insets: Insets(
|
||||
top: inset, bottom: inset, left: inset, right: inset
|
||||
)))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
inset: Double
|
||||
) -> Constraint {
|
||||
return .edges(Edges(target: target, insets: Insets(
|
||||
top: inset, bottom: inset, left: inset, right: inset
|
||||
)))
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
//
|
||||
// FirstBaseline.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct FirstBaseline: Equatable {
|
||||
public let target: Constraint.Target
|
||||
public let constant: Double
|
||||
public let multiplier: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0,
|
||||
multiplier: Double = 1
|
||||
) -> Constraint {
|
||||
return .firstBaseline(FirstBaseline(
|
||||
target: target, constant: constant, multiplier: multiplier
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// Height.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Height: Equatable {
|
||||
public let target: Constraint.OwnTarget
|
||||
public let constant: Double
|
||||
public let multiplier: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0,
|
||||
multiplier: Double = 1
|
||||
) -> Constraint {
|
||||
return .height(Height(
|
||||
target: .external(target), constant: constant, multiplier: multiplier
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.OwnTarget,
|
||||
constant: Double = 0,
|
||||
multiplier: Double = 1
|
||||
) -> Constraint {
|
||||
return .height(Height(
|
||||
target: target, constant: constant, multiplier: multiplier
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(to constant: Double) -> Constraint {
|
||||
return .height(Height(
|
||||
target: .own, constant: constant, multiplier: 0
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
//
|
||||
// LastBaseline.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct LastBaseline: Equatable {
|
||||
public let target: Constraint.Target
|
||||
public let constant: Double
|
||||
public let multiplier: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0,
|
||||
multiplier: Double = 1
|
||||
) -> Constraint {
|
||||
return .lastBaseline(LastBaseline(
|
||||
target: target, constant: constant, multiplier: multiplier
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// Leading.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Leading: Equatable {
|
||||
public let target: Constraint.SafeAreaTarget
|
||||
public let constant: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .leading(Leading(
|
||||
target: target, constant: constant
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .leading(Leading(
|
||||
target: .external(target), constant: constant
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// Left.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Left: Equatable {
|
||||
public let target: Constraint.SafeAreaTarget
|
||||
public let constant: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .left(Left(
|
||||
target: target, constant: constant
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .left(Left(
|
||||
target: .external(target), constant: constant
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// Right.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Right: Equatable {
|
||||
public let target: Constraint.SafeAreaTarget
|
||||
public let constant: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .right(Right(
|
||||
target: target, constant: constant
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .right(Right(
|
||||
target: .external(target), constant: constant
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// SizeConstraint.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public enum SizeConstraint: Equatable {
|
||||
case constant(Size)
|
||||
case multiplier(Constraint.Target, Double)
|
||||
}
|
||||
|
||||
extension Size {
|
||||
static func equal(to size: Size) -> SizeConstraint {
|
||||
return .constant(size)
|
||||
}
|
||||
|
||||
static func equal(
|
||||
to target: Constraint.Target,
|
||||
multiplier: Double = 1.0
|
||||
) -> SizeConstraint {
|
||||
return .multiplier(target, multiplier)
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// Top.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Top: Equatable {
|
||||
public let target: Constraint.SafeAreaTarget
|
||||
public let constant: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .top(Top(
|
||||
target: target, constant: constant
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .top(Top(
|
||||
target: .external(target), constant: constant
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// Trailing.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Trailing: Equatable {
|
||||
public let target: Constraint.SafeAreaTarget
|
||||
public let constant: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.SafeAreaTarget,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .trailing(Trailing(
|
||||
target: target, constant: constant
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0
|
||||
) -> Constraint {
|
||||
return .trailing(Trailing(
|
||||
target: .external(target), constant: constant
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// Width.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/02/2019.
|
||||
//
|
||||
|
||||
public struct Width: Equatable {
|
||||
public let target: Constraint.OwnTarget
|
||||
public let constant: Double
|
||||
public let multiplier: Double
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.Target,
|
||||
constant: Double = 0,
|
||||
multiplier: Double = 1
|
||||
) -> Constraint {
|
||||
return .width(Width(
|
||||
target: .external(target), constant: constant, multiplier: multiplier
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(
|
||||
to target: Constraint.OwnTarget,
|
||||
constant: Double = 0,
|
||||
multiplier: Double = 1
|
||||
) -> Constraint {
|
||||
return .width(Width(
|
||||
target: target, constant: constant, multiplier: multiplier
|
||||
))
|
||||
}
|
||||
|
||||
public static func equal(to constant: Double) -> Constraint {
|
||||
return .width(Width(
|
||||
target: .own, constant: constant, multiplier: 0
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
//
|
||||
// Event.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 04/12/2018.
|
||||
//
|
||||
|
||||
public enum Event: CaseIterable, Hashable {
|
||||
case touchDown
|
||||
case touchDownRepeat
|
||||
case touchDragInside
|
||||
case touchDragOutside
|
||||
case touchDragEnter
|
||||
case touchDragExit
|
||||
case touchUpInside
|
||||
case touchUpOutside
|
||||
case touchCancel
|
||||
case valueChanged
|
||||
case editingDidBegin
|
||||
case editingChanged
|
||||
case editingDidEnd
|
||||
case editingDidEndOnExit
|
||||
case allTouchEvents
|
||||
case allEditingEvents
|
||||
case allEvents
|
||||
|
||||
// FIXME: provisional stability, taken from Marzipan preview symbols
|
||||
// https://www.highcaffeinecontent.com/blog/20190302-Making-Marzipan-Apps-Sing
|
||||
case hoverEnter
|
||||
case hoverExit
|
||||
case contextualMenu
|
||||
}
|
||||
|
||||
public typealias EventHandlers = [Event: Handler<()>]
|
||||
|
||||
public protocol ControlProps {
|
||||
var handlers: EventHandlers { get }
|
||||
var isEnabled: Bool { get }
|
||||
}
|
||||
|
||||
public protocol ValueControlProps {
|
||||
associatedtype Value
|
||||
|
||||
var value: Value { get }
|
||||
var valueHandler: Handler<Value>? { get }
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
//
|
||||
// Image.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 26/02/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Image: Equatable {
|
||||
public enum ContentMode {
|
||||
case scaleToFill
|
||||
case scaleAspectFit
|
||||
case scaleAspectFill
|
||||
case center
|
||||
case top
|
||||
case bottom
|
||||
case left
|
||||
case right
|
||||
case topLeft
|
||||
case topRight
|
||||
case bottomLeft
|
||||
case bottomRight
|
||||
}
|
||||
|
||||
public enum RenderingMode {
|
||||
case automatic
|
||||
case alwaysOriginal
|
||||
case alwaysTemplate
|
||||
}
|
||||
|
||||
public enum Source: Equatable {
|
||||
case name(String)
|
||||
case data(Data)
|
||||
}
|
||||
|
||||
public let contentMode: ContentMode
|
||||
|
||||
/// when changed initializes new image with given data or name
|
||||
public let source: Source
|
||||
|
||||
/// when changed creates new image with `withRenderingMode`
|
||||
public let renderingMode: RenderingMode
|
||||
|
||||
/// equivalent to `flipsForRightToLeftLayoutDirection` in UIKit
|
||||
public let flipsForRTL: Bool
|
||||
|
||||
/// when changed initializes new image with given scale
|
||||
public let scale: Double
|
||||
|
||||
public init(
|
||||
contentMode: ContentMode = .scaleToFill,
|
||||
flipsForRTL: Bool = false,
|
||||
name: String,
|
||||
renderingMode: RenderingMode = .automatic,
|
||||
scale: Double = 1.0
|
||||
) {
|
||||
source = .name(name)
|
||||
self.contentMode = contentMode
|
||||
self.renderingMode = renderingMode
|
||||
self.scale = scale
|
||||
self.flipsForRTL = flipsForRTL
|
||||
}
|
||||
|
||||
public init(
|
||||
contentMode: ContentMode = .scaleToFill,
|
||||
data: Data,
|
||||
flipsForRTL: Bool = false,
|
||||
renderingMode: RenderingMode = .automatic,
|
||||
scale: Double = 1.0
|
||||
) {
|
||||
source = .data(data)
|
||||
self.contentMode = contentMode
|
||||
self.renderingMode = renderingMode
|
||||
self.scale = scale
|
||||
self.flipsForRTL = flipsForRTL
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// Insets.swift
|
||||
// TokamakUIKit
|
||||
//
|
||||
// Created by Max Desiatov on 17/01/2019.
|
||||
//
|
||||
|
||||
public struct Insets: Equatable {
|
||||
public let top: Double
|
||||
public let bottom: Double
|
||||
public let left: Double
|
||||
public let right: Double
|
||||
|
||||
public init(top: Double, bottom: Double, left: Double, right: Double) {
|
||||
self.top = top
|
||||
self.bottom = bottom
|
||||
self.left = left
|
||||
self.right = right
|
||||
}
|
||||
|
||||
public static var zero: Insets {
|
||||
return .init(top: 0, bottom: 0, left: 0, right: 0)
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
//
|
||||
// ScrollOptions.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 3/27/19.
|
||||
//
|
||||
|
||||
public struct ScrollOptions: Equatable, Default {
|
||||
public static var defaultValue: ScrollOptions {
|
||||
return ScrollOptions()
|
||||
}
|
||||
|
||||
public struct EdgeInsets: Equatable {
|
||||
public let bottom: Float
|
||||
public let left: Float
|
||||
public let right: Float
|
||||
public let top: Float
|
||||
|
||||
public init(
|
||||
top: Float = 0,
|
||||
left: Float = 0,
|
||||
bottom: Float = 0,
|
||||
right: Float = 0
|
||||
) {
|
||||
self.top = top
|
||||
self.left = left
|
||||
self.bottom = bottom
|
||||
self.right = right
|
||||
}
|
||||
}
|
||||
|
||||
public enum IndicatorStyle {
|
||||
case `default`
|
||||
case black
|
||||
case white
|
||||
}
|
||||
|
||||
public let alwaysBounceHorizontal: Bool
|
||||
public let alwaysBounceVertical: Bool
|
||||
public let bounces: Bool
|
||||
public let bouncesZoom: Bool
|
||||
public let contentInset: EdgeInsets
|
||||
public let indicatorStyle: IndicatorStyle
|
||||
public let isDirectionalLockEnabled: Bool
|
||||
public let isPagingEnabled: Bool
|
||||
public let isScrollEnabled: Bool
|
||||
public let maximumZoomScale: Float
|
||||
public let minimumZoomScale: Float
|
||||
public let scrollIndicatorInsets: EdgeInsets
|
||||
public let scrollsToTop: Bool
|
||||
public let showsHorizontalScrollIndicator: Bool
|
||||
public let showsVerticalScrollIndicator: Bool
|
||||
public let zoomScale: Float
|
||||
|
||||
public init(
|
||||
alwaysBounceHorizontal: Bool = false,
|
||||
alwaysBounceVertical: Bool = false,
|
||||
bounces: Bool = true,
|
||||
bouncesZoom: Bool = true,
|
||||
contentInset: EdgeInsets = EdgeInsets(),
|
||||
indicatorStyle: IndicatorStyle = .default,
|
||||
isDirectionalLockEnabled: Bool = false,
|
||||
isPagingEnabled: Bool = false,
|
||||
isScrollEnabled: Bool = true,
|
||||
maximumZoomScale: Float = 1.0,
|
||||
minimumZoomScale: Float = 1.0,
|
||||
scrollIndicatorInsets: EdgeInsets = EdgeInsets(),
|
||||
scrollsToTop: Bool = true,
|
||||
showsHorizontalScrollIndicator: Bool = true,
|
||||
showsVerticalScrollIndicator: Bool = true,
|
||||
zoomScale: Float = 1.0
|
||||
) {
|
||||
self.alwaysBounceHorizontal = alwaysBounceHorizontal
|
||||
self.alwaysBounceVertical = alwaysBounceVertical
|
||||
self.bounces = bounces
|
||||
self.bouncesZoom = bouncesZoom
|
||||
self.contentInset = contentInset
|
||||
self.isDirectionalLockEnabled = isDirectionalLockEnabled
|
||||
self.isPagingEnabled = isPagingEnabled
|
||||
self.indicatorStyle = indicatorStyle
|
||||
self.isScrollEnabled = isScrollEnabled
|
||||
self.maximumZoomScale = maximumZoomScale
|
||||
self.minimumZoomScale = minimumZoomScale
|
||||
self.scrollIndicatorInsets = scrollIndicatorInsets
|
||||
self.scrollsToTop = scrollsToTop
|
||||
self.showsVerticalScrollIndicator = showsVerticalScrollIndicator
|
||||
self.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator
|
||||
self.zoomScale = zoomScale
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// Second.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 05/01/2019.
|
||||
//
|
||||
|
||||
public struct Second: ExpressibleByFloatLiteral, Equatable {
|
||||
public let value: Double
|
||||
|
||||
public init(floatLiteral value: Double) {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
public init(_ value: Double) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
//
|
||||
// Style.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 31/12/2018.
|
||||
//
|
||||
|
||||
public protocol StyleProps {
|
||||
var style: Style? { get }
|
||||
}
|
||||
|
||||
public enum Layout: Equatable {
|
||||
case frame(Rectangle)
|
||||
case constraints([Constraint])
|
||||
}
|
||||
|
||||
public struct Style: Equatable {
|
||||
public let accessibility: Accessibility?
|
||||
public let allowsEdgeAntialiasing: Bool?
|
||||
public let allowsGroupOpacity: Bool?
|
||||
public let backgroundColor: Color?
|
||||
public let borderColor: Color?
|
||||
public let borderWidth: Float?
|
||||
public let cornerRadius: Float?
|
||||
public let isDoubleSided: Bool?
|
||||
public let isHidden: Bool?
|
||||
public let masksToBounds: Bool?
|
||||
public let opacity: Float?
|
||||
public let shadowColor: Color?
|
||||
public let shadowOpacity: Float?
|
||||
public let shadowRadius: Float?
|
||||
public let layout: Layout?
|
||||
|
||||
public init(
|
||||
accessibility: Accessibility? = nil,
|
||||
allowsEdgeAntialiasing: Bool? = false,
|
||||
allowsGroupOpacity: Bool? = true,
|
||||
backgroundColor: Color? = nil,
|
||||
borderColor: Color? = nil,
|
||||
borderWidth: Float? = 0.0,
|
||||
cornerRadius: Float? = 0.0,
|
||||
isDoubleSided: Bool? = true,
|
||||
isHidden: Bool? = nil,
|
||||
masksToBounds: Bool? = false,
|
||||
opacity: Float? = 1.0,
|
||||
shadowColor: Color? = nil,
|
||||
shadowOpacity: Float? = 0.0,
|
||||
shadowRadius: Float? = 3.0
|
||||
) {
|
||||
self.accessibility = accessibility
|
||||
self.allowsEdgeAntialiasing = allowsEdgeAntialiasing
|
||||
self.allowsGroupOpacity = allowsGroupOpacity
|
||||
self.backgroundColor = backgroundColor
|
||||
self.borderColor = borderColor
|
||||
self.borderWidth = borderWidth
|
||||
self.cornerRadius = cornerRadius
|
||||
self.isDoubleSided = isDoubleSided
|
||||
self.isHidden = isHidden
|
||||
self.masksToBounds = masksToBounds
|
||||
self.opacity = opacity
|
||||
self.shadowColor = shadowColor
|
||||
self.shadowOpacity = shadowOpacity
|
||||
self.shadowRadius = shadowRadius
|
||||
|
||||
layout = nil
|
||||
}
|
||||
|
||||
public init(
|
||||
_ frame: Rectangle,
|
||||
accessibility: Accessibility? = nil,
|
||||
allowsEdgeAntialiasing: Bool? = false,
|
||||
allowsGroupOpacity: Bool? = true,
|
||||
backgroundColor: Color? = nil,
|
||||
borderColor: Color? = nil,
|
||||
borderWidth: Float? = 0.0,
|
||||
cornerRadius: Float? = 0.0,
|
||||
isDoubleSided: Bool? = true,
|
||||
isHidden: Bool? = nil,
|
||||
masksToBounds: Bool? = false,
|
||||
opacity: Float? = 1.0,
|
||||
shadowColor: Color? = nil,
|
||||
shadowOpacity: Float? = 0.0,
|
||||
shadowRadius: Float? = 3.0
|
||||
) {
|
||||
self.accessibility = accessibility
|
||||
self.allowsEdgeAntialiasing = allowsEdgeAntialiasing
|
||||
self.allowsGroupOpacity = allowsGroupOpacity
|
||||
self.backgroundColor = backgroundColor
|
||||
self.borderColor = borderColor
|
||||
self.borderWidth = borderWidth
|
||||
self.cornerRadius = cornerRadius
|
||||
self.isDoubleSided = isDoubleSided
|
||||
self.isHidden = isHidden
|
||||
self.masksToBounds = masksToBounds
|
||||
self.opacity = opacity
|
||||
self.shadowColor = shadowColor
|
||||
self.shadowOpacity = shadowOpacity
|
||||
self.shadowRadius = shadowRadius
|
||||
|
||||
layout = .frame(frame)
|
||||
}
|
||||
|
||||
public init(
|
||||
_ constraint: Constraint,
|
||||
accessibility: Accessibility? = nil,
|
||||
allowsEdgeAntialiasing: Bool? = false,
|
||||
allowsGroupOpacity: Bool? = true,
|
||||
backgroundColor: Color? = nil,
|
||||
borderColor: Color? = nil,
|
||||
borderWidth: Float? = 0.0,
|
||||
cornerRadius: Float? = 0.0,
|
||||
isDoubleSided: Bool? = true,
|
||||
isHidden: Bool? = nil,
|
||||
masksToBounds: Bool? = false,
|
||||
opacity: Float? = 1.0,
|
||||
shadowColor: Color? = nil,
|
||||
shadowOpacity: Float? = 0.0,
|
||||
shadowRadius: Float? = 3.0
|
||||
) {
|
||||
self.accessibility = accessibility
|
||||
self.allowsEdgeAntialiasing = allowsEdgeAntialiasing
|
||||
self.allowsGroupOpacity = allowsGroupOpacity
|
||||
self.backgroundColor = backgroundColor
|
||||
self.borderColor = borderColor
|
||||
self.borderWidth = borderWidth
|
||||
self.cornerRadius = cornerRadius
|
||||
self.isDoubleSided = isDoubleSided
|
||||
self.isHidden = isHidden
|
||||
self.masksToBounds = masksToBounds
|
||||
self.opacity = opacity
|
||||
self.shadowColor = shadowColor
|
||||
self.shadowOpacity = shadowOpacity
|
||||
self.shadowRadius = shadowRadius
|
||||
|
||||
layout = .constraints([constraint])
|
||||
}
|
||||
|
||||
public init(
|
||||
_ constraints: [Constraint],
|
||||
accessibility: Accessibility? = nil,
|
||||
allowsEdgeAntialiasing: Bool? = false,
|
||||
allowsGroupOpacity: Bool? = true,
|
||||
backgroundColor: Color? = nil,
|
||||
borderColor: Color? = nil,
|
||||
borderWidth: Float? = 0.0,
|
||||
cornerRadius: Float? = 0.0,
|
||||
isDoubleSided: Bool? = true,
|
||||
isHidden: Bool? = nil,
|
||||
masksToBounds: Bool? = false,
|
||||
opacity: Float? = 1.0,
|
||||
shadowColor: Color? = nil,
|
||||
shadowOpacity: Float? = 0.0,
|
||||
shadowRadius: Float? = 3.0
|
||||
) {
|
||||
self.accessibility = accessibility
|
||||
self.allowsEdgeAntialiasing = allowsEdgeAntialiasing
|
||||
self.allowsGroupOpacity = allowsGroupOpacity
|
||||
self.backgroundColor = backgroundColor
|
||||
self.borderColor = borderColor
|
||||
self.borderWidth = borderWidth
|
||||
self.cornerRadius = cornerRadius
|
||||
self.isDoubleSided = isDoubleSided
|
||||
self.isHidden = isHidden
|
||||
self.masksToBounds = masksToBounds
|
||||
self.opacity = opacity
|
||||
self.shadowColor = shadowColor
|
||||
self.shadowOpacity = shadowOpacity
|
||||
self.shadowRadius = shadowRadius
|
||||
|
||||
layout = .constraints(constraints)
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
//
|
||||
// Effect.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 10/02/2019.
|
||||
//
|
||||
|
||||
extension Hooks {
|
||||
/// Schedule an effect to be executed on every call to `render`.
|
||||
public func effect(closure: @escaping () -> ()) {
|
||||
scheduleEffect(nil) { closure(); return nil }
|
||||
}
|
||||
|
||||
/** Schedule an effect to be executed on every call to `render`. The effect
|
||||
closure should return a cleanup closure to be executed before the next
|
||||
call to `render` or when a component is unmounted.
|
||||
*/
|
||||
public func finalizedEffect(closure: @escaping () -> () -> ()) {
|
||||
scheduleEffect(nil, closure)
|
||||
}
|
||||
|
||||
/** Schedule an effect to be executed on calls to `render` when `observed`
|
||||
value has changed from the previous call to `render`.
|
||||
|
||||
You can use this overload of `effect` to control when exactly the effect
|
||||
`closure` is executed. For example, always pass `Null()` as `observed` so
|
||||
that the effect is executed only once on the initial rendering.
|
||||
|
||||
Another use case is an effect that schedules a repeated timer with a specific
|
||||
interval. You wouldn't want to reschedule the timer on every call to
|
||||
component's `render` if the interval hasn't changed. Pass the interval as
|
||||
`observed`, which will be compared to the previous value and trigger effect
|
||||
execution (updating the timer interval or creating a new timer) when the
|
||||
interval has changed.
|
||||
*/
|
||||
public func effect<T: Equatable>(_ observed: T, closure: @escaping () -> ()) {
|
||||
scheduleEffect(AnyEquatable(observed)) { closure(); return nil }
|
||||
}
|
||||
|
||||
/** Schedule an effect to be executed on calls to `render` when `observed`
|
||||
value has changed from the previous call to `render`. The effect
|
||||
closure should return a cleanup closure to be executed before the next
|
||||
call to `render` or when a component is unmounted.
|
||||
|
||||
You can use this overload of `effect` to control when exactly the effect
|
||||
`closure` is executed with additional cleanup. For example, always pass
|
||||
`Null()` as `observed` so that the effect is executed only once on the
|
||||
initial rendering.
|
||||
|
||||
Another use case is an effect that subscribes to updates on a user model
|
||||
fetched from the network and you need to correctly unsubscribe from updates
|
||||
when a component is unmounted. You wouldn't to unsubscribe and resubscribe
|
||||
every time a component rerendered if an observed user ID hasn't changed.
|
||||
Pass the ID as `observed`, which will be compared to the previous value and
|
||||
trigger effect execution (unsubscribing from updates on old user ID and
|
||||
subscribing for new user ID) when the ID has changed.
|
||||
*/
|
||||
public func finalizedEffect<T>(
|
||||
_ observed: T,
|
||||
closure: @escaping () -> () -> ()
|
||||
)
|
||||
where T: Equatable {
|
||||
scheduleEffect(AnyEquatable(observed), closure)
|
||||
}
|
||||
}
|
|
@ -11,16 +11,6 @@ typealias Effect = () -> Finalizer
|
|||
protocol HookedComponent: AnyObject {
|
||||
/// State cells of this component indexed by order of `hooks.state` calls
|
||||
var state: [Any] { get set }
|
||||
|
||||
/// Effect cells of this component indexed by order of `hooks.effect` calls
|
||||
var effects: [(observed: AnyEquatable?, Effect)] { get set }
|
||||
|
||||
/// Finalizer cells of this component received from `Effect` evaluation.
|
||||
/// Indices in this array exactly match indices in `effects` array.
|
||||
var effectFinalizers: [Finalizer] { get set }
|
||||
|
||||
/// Ref cells of this component indexed by order of `hooks.ref` calls
|
||||
var refs: [AnyObject] { get set }
|
||||
}
|
||||
|
||||
/** Functions implemented directly in this class are parts of internal
|
||||
|
@ -40,11 +30,6 @@ public final class Hooks {
|
|||
|
||||
private var stateIndex = 0
|
||||
|
||||
private var effectIndex = 0
|
||||
private(set) var scheduledEffects = Set<Int>()
|
||||
|
||||
private var refIndex = 0
|
||||
|
||||
init(
|
||||
component: HookedComponent,
|
||||
queueState: @escaping (
|
||||
|
@ -74,57 +59,4 @@ public final class Hooks {
|
|||
// swiftlint:disable:next force_cast
|
||||
return ({ component.state[boundIndex] as! T }, stateIndex)
|
||||
}
|
||||
|
||||
/** Schedules effect exection with the current reconciler accessed via
|
||||
`component`.
|
||||
*/
|
||||
func scheduleEffect(
|
||||
_ observed: AnyEquatable?,
|
||||
_ effect: @escaping Effect
|
||||
) {
|
||||
defer { effectIndex += 1 }
|
||||
|
||||
guard let component = component else {
|
||||
assertionFailure("hooks.effect should only be called within `render`")
|
||||
return
|
||||
}
|
||||
|
||||
if component.effects.count > effectIndex {
|
||||
guard component.effects[effectIndex].0 != observed else { return }
|
||||
|
||||
component.effects[effectIndex].0 = observed
|
||||
component.effects[effectIndex].1 = effect
|
||||
scheduledEffects.insert(effectIndex)
|
||||
} else {
|
||||
component.effects.append((observed, effect))
|
||||
scheduledEffects.insert(effectIndex)
|
||||
}
|
||||
}
|
||||
|
||||
/** For a given initial value return a current ref
|
||||
(initialized from `initial` if current was absent)
|
||||
*/
|
||||
func ref<T>(_ initial: Ref<T>) -> Ref<T> {
|
||||
defer { refIndex += 1 }
|
||||
|
||||
guard let component = component else {
|
||||
assertionFailure("hooks.ref should only be called within `render`")
|
||||
return initial
|
||||
}
|
||||
|
||||
if component.refs.count > refIndex {
|
||||
guard let result = component.refs[refIndex] as? Ref<T> else {
|
||||
assertionFailure(
|
||||
"""
|
||||
unexpected ref type during rendering, possible Rules of Hooks violation
|
||||
"""
|
||||
)
|
||||
return initial
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
component.refs.append(initial)
|
||||
return initial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// Ref.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 09/02/2019.
|
||||
//
|
||||
|
||||
public final class Ref<T> {
|
||||
public var value: T
|
||||
|
||||
init(_ value: T) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
extension Hooks {
|
||||
public func ref<T>(type: T.Type) -> Ref<T?> {
|
||||
return ref(Ref(nil))
|
||||
}
|
||||
|
||||
public func ref<T>(_ initial: T) -> Ref<T> {
|
||||
return ref(Ref(initial))
|
||||
}
|
||||
}
|
|
@ -6,10 +6,12 @@
|
|||
//
|
||||
|
||||
public class MountedComponent<R: Renderer> {
|
||||
public internal(set) var node: AnyNode
|
||||
public internal(set) var node: AnyView
|
||||
public let viewType: Any.Type
|
||||
|
||||
init(_ node: AnyNode) {
|
||||
init(_ node: AnyView) {
|
||||
self.node = node
|
||||
viewType = node.type
|
||||
}
|
||||
|
||||
func mount(with reconciler: StackReconciler<R>) {
|
||||
|
@ -25,16 +27,25 @@ public class MountedComponent<R: Renderer> {
|
|||
}
|
||||
}
|
||||
|
||||
extension AnyNode {
|
||||
extension View {
|
||||
func makeMountedComponent<R: Renderer>(_ parentTarget: R.TargetType)
|
||||
-> MountedComponent<R> {
|
||||
switch type {
|
||||
case let .host(type):
|
||||
return MountedHostComponent(self, type, parentTarget)
|
||||
case let .composite(type):
|
||||
return MountedCompositeComponent(self, type, parentTarget)
|
||||
case .null:
|
||||
return MountedNull(self)
|
||||
if let anyView = self as? AnyView {
|
||||
if anyView.type == EmptyView.self {
|
||||
return MountedNull(anyView)
|
||||
} else if anyView.bodyType == Never.self {
|
||||
return MountedHostComponent(anyView, parentTarget)
|
||||
} else {
|
||||
return MountedCompositeComponent(anyView, parentTarget)
|
||||
}
|
||||
}
|
||||
|
||||
if self is EmptyView {
|
||||
return MountedNull(AnyView(self))
|
||||
} else if Body.self is Never.Type {
|
||||
return MountedHostComponent(AnyView(self), parentTarget)
|
||||
} else {
|
||||
return MountedCompositeComponent(AnyView(self), parentTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
//
|
||||
|
||||
import Dispatch
|
||||
import Runtime
|
||||
|
||||
final class MountedCompositeComponent<R: Renderer>: MountedComponent<R>,
|
||||
HookedComponent, Hashable {
|
||||
static func ==(lhs: MountedCompositeComponent<R>,
|
||||
rhs: MountedCompositeComponent<R>) -> Bool {
|
||||
return lhs === rhs
|
||||
lhs === rhs
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
|
@ -20,22 +21,12 @@ final class MountedCompositeComponent<R: Renderer>: MountedComponent<R>,
|
|||
|
||||
private var mountedChildren = [MountedComponent<R>]()
|
||||
private let parentTarget: R.TargetType
|
||||
let type: AnyCompositeComponent.Type
|
||||
|
||||
// HookedComponent implementation
|
||||
|
||||
/// There's no easy way to downcast elements of `[Any]` to `T`
|
||||
/// and apply `inout` updater without creating copies, working around
|
||||
/// that with pointers.
|
||||
var state = [Any]()
|
||||
var effects = [(observed: AnyEquatable?, Effect)]()
|
||||
var effectFinalizers = [Finalizer]()
|
||||
var refs = [AnyObject]()
|
||||
|
||||
init(_ node: AnyNode,
|
||||
_ type: AnyCompositeComponent.Type,
|
||||
init(_ node: AnyView,
|
||||
_ parentTarget: R.TargetType) {
|
||||
self.type = type
|
||||
self.parentTarget = parentTarget
|
||||
|
||||
super.init(node)
|
||||
|
@ -44,24 +35,17 @@ final class MountedCompositeComponent<R: Renderer>: MountedComponent<R>,
|
|||
override func mount(with reconciler: StackReconciler<R>) {
|
||||
let renderedNode = reconciler.render(component: self)
|
||||
|
||||
let child: MountedComponent<R> =
|
||||
renderedNode.makeMountedComponent(parentTarget)
|
||||
let child: MountedComponent<R> = renderedNode.makeMountedComponent(parentTarget)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
}
|
||||
|
||||
override func unmount(with reconciler: StackReconciler<R>) {
|
||||
mountedChildren.forEach { $0.unmount(with: reconciler) }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
for f in self.effectFinalizers {
|
||||
f?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func update(with reconciler: StackReconciler<R>) {
|
||||
// FIXME: for now without fragments mounted composite components have only
|
||||
// FIXME: for now without fragments (groups?) mounted composite components have only
|
||||
// a single element in `mountedChildren`, but this will change when
|
||||
// fragments are implemented and this switch should be rewritten to compare
|
||||
// all elements in `mountedChildren`
|
||||
|
@ -75,16 +59,21 @@ final class MountedCompositeComponent<R: Renderer>: MountedComponent<R>,
|
|||
|
||||
// some mounted children
|
||||
case let (wrapper?, renderedNode):
|
||||
// new node is the same type as existing child, checking props/children
|
||||
if wrapper.node.type == renderedNode.type,
|
||||
wrapper.node.props != renderedNode.props ||
|
||||
wrapper.node.children != renderedNode.children {
|
||||
wrapper.node = renderedNode
|
||||
let renderedNodeType = (renderedNode as? AnyView)?.type ?? type(of: renderedNode)
|
||||
|
||||
// FIXME: no idea if using `mangledName` is reliable, but seems to be the only way to get
|
||||
// a name of a type constructor in runtime. Should definitely check if these are different
|
||||
// across modules, otherwise can cause problems with views with same names in different
|
||||
// modules.
|
||||
|
||||
// new node is the same type as existing child
|
||||
// swiftlint:disable:next force_try
|
||||
if try! wrapper.node.typeConstructorName == typeInfo(of: renderedNodeType).mangledName {
|
||||
wrapper.node = AnyView(renderedNode)
|
||||
wrapper.update(with: reconciler)
|
||||
} else
|
||||
// new node is of different type, complete rerender, i.e. unmount old
|
||||
// wrapper, then mount a new one with new node
|
||||
if wrapper.node.type != renderedNode.type {
|
||||
} else {
|
||||
// new node is of different type, complete rerender, i.e. unmount old
|
||||
// wrapper, then mount a new one with new node
|
||||
wrapper.unmount(with: reconciler)
|
||||
|
||||
let child: MountedComponent<R> =
|
||||
|
|
|
@ -23,12 +23,8 @@ public final class MountedHostComponent<R: Renderer>: MountedComponent<R> {
|
|||
*/
|
||||
private var target: R.TargetType?
|
||||
|
||||
public let type: AnyHostComponent.Type
|
||||
|
||||
init(_ node: AnyNode,
|
||||
_ type: AnyHostComponent.Type,
|
||||
init(_ node: AnyView,
|
||||
_ parentTarget: R.TargetType) {
|
||||
self.type = type
|
||||
self.parentTarget = parentTarget
|
||||
|
||||
super.init(node)
|
||||
|
@ -44,21 +40,10 @@ public final class MountedHostComponent<R: Renderer>: MountedComponent<R> {
|
|||
|
||||
reconciler.renderer?.update(target: target, with: self)
|
||||
|
||||
switch node.children.value {
|
||||
case let nodes as [AnyNode]:
|
||||
mountedChildren = nodes.map { $0.makeMountedComponent(target) }
|
||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||
guard !node.children.isEmpty else { return }
|
||||
|
||||
case let node as AnyNode:
|
||||
let child: MountedComponent<R> = node.makeMountedComponent(target)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
|
||||
default:
|
||||
// child type that can't be rendered, but still makes sense
|
||||
// (e.g. `String`)
|
||||
()
|
||||
}
|
||||
mountedChildren = node.children.map { $0.makeMountedComponent(target) }
|
||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||
}
|
||||
|
||||
override func unmount(with reconciler: StackReconciler<R>) {
|
||||
|
@ -80,93 +65,64 @@ public final class MountedHostComponent<R: Renderer>: MountedComponent<R> {
|
|||
reconciler.renderer?.update(target: target,
|
||||
with: self)
|
||||
|
||||
switch node.children.value {
|
||||
case var nodes as [AnyNode]:
|
||||
switch (mountedChildren.isEmpty, nodes.isEmpty) {
|
||||
// if existing children present and new children array is empty
|
||||
// then unmount all existing children
|
||||
case (false, true):
|
||||
mountedChildren.forEach { $0.unmount(with: reconciler) }
|
||||
mountedChildren = []
|
||||
var nodes = node.children
|
||||
|
||||
// if no existing children then mount all new children
|
||||
case (true, false):
|
||||
mountedChildren = nodes.map { $0.makeMountedComponent(target) }
|
||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||
switch (mountedChildren.isEmpty, nodes.isEmpty) {
|
||||
// if existing children present and new children array is empty
|
||||
// then unmount all existing children
|
||||
case (false, true):
|
||||
mountedChildren.forEach { $0.unmount(with: reconciler) }
|
||||
mountedChildren = []
|
||||
|
||||
// if both arrays have items then reconcile by types and keys
|
||||
case (false, false):
|
||||
var newChildren = [MountedComponent<R>]()
|
||||
// if no existing children then mount all new children
|
||||
case (true, false):
|
||||
mountedChildren = nodes.map { $0.makeMountedComponent(target) }
|
||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||
|
||||
// iterate through every `mountedChildren` element and compare with
|
||||
// a corresponding `nodes` element, remount if type differs, otherwise
|
||||
// run simple update
|
||||
while let child = mountedChildren.first, let node = nodes.first {
|
||||
let newChild: MountedComponent<R>
|
||||
if node.type == mountedChildren[0].node.type {
|
||||
// not sure if comparison of `props` between `child.node` and
|
||||
// `node` are more computationally expensive than plainly
|
||||
// updating props on a target. `children` comparison between
|
||||
// `child.node` and `node` runs implicitly within this loop.
|
||||
// From functionality perspective this should work and we need
|
||||
// benchmarks before this can be optimised.
|
||||
child.node = node
|
||||
child.update(with: reconciler)
|
||||
newChild = child
|
||||
} else {
|
||||
child.unmount(with: reconciler)
|
||||
newChild = node.makeMountedComponent(target)
|
||||
newChild.mount(with: reconciler)
|
||||
}
|
||||
newChildren.append(newChild)
|
||||
mountedChildren.removeFirst()
|
||||
nodes.removeFirst()
|
||||
}
|
||||
// if both arrays have items then reconcile by types and keys
|
||||
case (false, false):
|
||||
var newChildren = [MountedComponent<R>]()
|
||||
|
||||
// more mounted components left than nodes were to be rendered:
|
||||
// unmount remaining `mountedChildren`
|
||||
if !mountedChildren.isEmpty {
|
||||
for child in mountedChildren {
|
||||
child.unmount(with: reconciler)
|
||||
}
|
||||
} else {
|
||||
// more nodes left than children were mounted,
|
||||
// mount remaining nodes
|
||||
for node in nodes {
|
||||
let newChild: MountedComponent<R> =
|
||||
node.makeMountedComponent(target)
|
||||
newChild.mount(with: reconciler)
|
||||
newChildren.append(newChild)
|
||||
}
|
||||
}
|
||||
|
||||
mountedChildren = newChildren
|
||||
|
||||
// both arrays are empty, nothing to reconcile
|
||||
case (true, true):
|
||||
()
|
||||
}
|
||||
|
||||
case let node as AnyNode:
|
||||
if let child = mountedChildren.first {
|
||||
if child.node.type == node.type {
|
||||
// iterate through every `mountedChildren` element and compare with
|
||||
// a corresponding `nodes` element, remount if type differs, otherwise
|
||||
// run simple update
|
||||
while let child = mountedChildren.first, let node = nodes.first {
|
||||
let newChild: MountedComponent<R>
|
||||
if node.typeConstructorName == mountedChildren[0].node.typeConstructorName {
|
||||
child.node = node
|
||||
child.update(with: reconciler)
|
||||
newChild = child
|
||||
} else {
|
||||
child.unmount(with: reconciler)
|
||||
let child: MountedComponent<R> = node.makeMountedComponent(target)
|
||||
child.mount(with: reconciler)
|
||||
mountedChildren = [child]
|
||||
newChild = node.makeMountedComponent(target)
|
||||
newChild.mount(with: reconciler)
|
||||
}
|
||||
} else {
|
||||
let child: MountedComponent<R> = node.makeMountedComponent(target)
|
||||
child.mount(with: reconciler)
|
||||
mountedChildren = [child]
|
||||
newChildren.append(newChild)
|
||||
mountedChildren.removeFirst()
|
||||
nodes.removeFirst()
|
||||
}
|
||||
|
||||
// child type that can't be rendered, but still makes sense as a child
|
||||
// (e.g. `String`)
|
||||
default:
|
||||
// more mounted components left than nodes were to be rendered:
|
||||
// unmount remaining `mountedChildren`
|
||||
if !mountedChildren.isEmpty {
|
||||
for child in mountedChildren {
|
||||
child.unmount(with: reconciler)
|
||||
}
|
||||
} else {
|
||||
// more nodes left than children were mounted,
|
||||
// mount remaining nodes
|
||||
for node in nodes {
|
||||
let newChild: MountedComponent<R> =
|
||||
node.makeMountedComponent(target)
|
||||
newChild.mount(with: reconciler)
|
||||
newChildren.append(newChild)
|
||||
}
|
||||
}
|
||||
|
||||
mountedChildren = newChildren
|
||||
|
||||
// both arrays are empty, nothing to reconcile
|
||||
case (true, true):
|
||||
()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ extension Color: ExpressibleByIntegerLiteral {
|
|||
|
||||
extension Color {
|
||||
public init?(hex: String) {
|
||||
let cArray = Array(hex.replacingOccurrences(of: "#", with: ""))
|
||||
let cArray = Array(hex.count > 6 ? String(hex.dropFirst()) : hex)
|
||||
|
||||
guard cArray.count == 6 else { return nil }
|
||||
|
|
@ -15,7 +15,7 @@ public struct Point: Equatable {
|
|||
}
|
||||
|
||||
public static var zero: Point {
|
||||
return Point(x: 0, y: 0)
|
||||
Point(x: 0, y: 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ public struct Size: Equatable {
|
|||
}
|
||||
|
||||
public static var zero: Size {
|
||||
return Size(width: 0, height: 0)
|
||||
Size(width: 0, height: 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,6 @@ public struct Rectangle: Equatable {
|
|||
}
|
||||
|
||||
public static var zero: Rectangle {
|
||||
return Rectangle(.zero, .zero)
|
||||
Rectangle(.zero, .zero)
|
||||
}
|
||||
}
|
|
@ -34,9 +34,6 @@ public protocol Renderer: AnyObject {
|
|||
instance.
|
||||
- parameter component: Type of the host component that renders to the
|
||||
newly created target.
|
||||
- parameter props: Props used to configure the new target.
|
||||
- parameter children: Children of the rendered host component for the new
|
||||
target.
|
||||
- returns: The newly created target.
|
||||
*/
|
||||
func mountTarget(
|
||||
|
@ -49,13 +46,6 @@ public protocol Renderer: AnyObject {
|
|||
- parameter target: Existing target instance to be updated.
|
||||
- parameter component: Type of the host component that renders to the
|
||||
updated target.
|
||||
- parameter props: Props used to configure the existing target. This props
|
||||
value can be different from props passed on previous
|
||||
updates or on target creation. The props value is wrapped
|
||||
with `AnyEquatable` for type-erasure purposes.
|
||||
- parameter children: Children used to configure the existing target. These
|
||||
children can be different from children passed on
|
||||
previous updates or on target creation.
|
||||
*/
|
||||
func update(
|
||||
target: TargetType,
|
||||
|
@ -78,7 +68,7 @@ public protocol Renderer: AnyObject {
|
|||
}
|
||||
|
||||
extension Renderer {
|
||||
public func mount(with node: AnyNode, to parent: TargetType) -> Mounted {
|
||||
public func mount(with node: AnyView, to parent: TargetType) -> Mounted {
|
||||
let result: Mounted = node.makeMountedComponent(parent)
|
||||
if let reconciler = reconciler {
|
||||
result.mount(with: reconciler)
|
||||
|
@ -86,7 +76,7 @@ extension Renderer {
|
|||
return result
|
||||
}
|
||||
|
||||
public func update(component: Mounted, with node: AnyNode) {
|
||||
public func update(component: Mounted, with node: AnyView) {
|
||||
component.node = node
|
||||
if let reconciler = reconciler {
|
||||
component.update(with: reconciler)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import Dispatch
|
||||
import Runtime
|
||||
|
||||
public final class StackReconciler<R: Renderer> {
|
||||
private var queuedRerenders = Set<MountedCompositeComponent<R>>()
|
||||
|
@ -14,7 +15,7 @@ public final class StackReconciler<R: Renderer> {
|
|||
private let rootComponent: MountedComponent<R>
|
||||
private(set) weak var renderer: R?
|
||||
|
||||
public init(node: AnyNode, target: R.TargetType, renderer: R) {
|
||||
public init<V: View>(node: V, target: R.TargetType, renderer: R) {
|
||||
self.renderer = renderer
|
||||
rootTarget = target
|
||||
|
||||
|
@ -23,9 +24,11 @@ public final class StackReconciler<R: Renderer> {
|
|||
rootComponent.mount(with: self)
|
||||
}
|
||||
|
||||
func queue(updater: (inout Any) -> (),
|
||||
for component: MountedCompositeComponent<R>,
|
||||
id: Int) {
|
||||
func queueUpdate(
|
||||
for component: MountedCompositeComponent<R>,
|
||||
id: Int,
|
||||
updater: (inout Any) -> ()
|
||||
) {
|
||||
let scheduleReconcile = queuedRerenders.isEmpty
|
||||
|
||||
updater(&component.state[id])
|
||||
|
@ -46,37 +49,34 @@ public final class StackReconciler<R: Renderer> {
|
|||
queuedRerenders.removeAll()
|
||||
}
|
||||
|
||||
func render(component: MountedCompositeComponent<R>) -> AnyNode {
|
||||
// Avoiding an indirect reference cycle here: this closure can be
|
||||
// owned by callbacks owned by node's target, which is strongly referenced
|
||||
// by the reconciler.
|
||||
let hooks = Hooks(
|
||||
component: component
|
||||
) { [weak self, weak component] id, updater in
|
||||
guard let component = component else { return }
|
||||
self?.queue(updater: updater, for: component, id: id)
|
||||
}
|
||||
func render(component: MountedCompositeComponent<R>) -> some View {
|
||||
// swiftlint:disable force_try
|
||||
let info = try! typeInfo(of: component.node.type)
|
||||
let stateProperties = info.properties.filter { $0.type is ValueStorage.Type }
|
||||
|
||||
let result = component.type.render(
|
||||
props: component.node.props,
|
||||
children: component.node.children,
|
||||
hooks: hooks
|
||||
)
|
||||
for (id, stateProperty) in stateProperties.enumerated() {
|
||||
// `ValueStorage` properties were already filtered out, so safe to assume the value's type
|
||||
// swiftlint:disable:next force_cast
|
||||
var state = try! stateProperty.get(from: component.node.view) as! ValueStorage
|
||||
|
||||
DispatchQueue.main.async {
|
||||
for i in hooks.scheduledEffects {
|
||||
if component.effectFinalizers.count > i {
|
||||
component.effectFinalizers[i]?()
|
||||
component.effectFinalizers[i] = component.effects[i].1()
|
||||
} else {
|
||||
component.effectFinalizers.append(component.effects[i].1())
|
||||
}
|
||||
if component.state.count == id {
|
||||
component.state.append(state.anyInitialValue)
|
||||
}
|
||||
}
|
||||
|
||||
// clean up `component` reference to enable assertions when hooks are called
|
||||
// outside of `render`
|
||||
hooks.component = nil
|
||||
state.getter = { component.state[id] }
|
||||
|
||||
// Avoiding an indirect reference cycle here: this closure can be
|
||||
// owned by callbacks owned by node's target, which is strongly referenced
|
||||
// by the reconciler.
|
||||
state.setter = { [weak self, weak component] newValue in
|
||||
guard let component = component else { return }
|
||||
self?.queueUpdate(for: component, id: id) { $0 = newValue }
|
||||
}
|
||||
try! stateProperty.set(value: state, on: &component.node.view)
|
||||
}
|
||||
// swiftlint:enable force_try
|
||||
|
||||
let result = component.node.bodyClosure(component.node.view)
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Created by Max Desiatov on 08/04/2020.
|
||||
//
|
||||
|
||||
protocol ValueStorage {
|
||||
var getter: (() -> Any)? { get set }
|
||||
var setter: ((Any) -> ())? { get set }
|
||||
var anyInitialValue: Any { get }
|
||||
}
|
||||
|
||||
@propertyWrapper public struct State<Value> {
|
||||
private let initialValue: Value
|
||||
|
||||
var anyInitialValue: Any { initialValue }
|
||||
|
||||
var getter: (() -> Any)?
|
||||
var setter: ((Any) -> ())?
|
||||
|
||||
public init(wrappedValue value: Value) {
|
||||
initialValue = value
|
||||
}
|
||||
|
||||
public var wrappedValue: Value {
|
||||
get { getter?() as? Value ?? initialValue }
|
||||
nonmutating set { setter?(newValue) }
|
||||
}
|
||||
|
||||
public var projectedValue: Binding<Value> {
|
||||
guard let getter = getter, let setter = setter else {
|
||||
fatalError("\(#function) not available outside of `body`")
|
||||
}
|
||||
// swiftlint:disable:next force_cast
|
||||
return .init(get: { getter() as! Value }, set: { setter($0) })
|
||||
}
|
||||
}
|
||||
|
||||
extension State: ValueStorage {}
|
||||
|
||||
extension State where Value: ExpressibleByNilLiteral {
|
||||
@inlinable public init() { self.init(wrappedValue: nil) }
|
||||
}
|
|
@ -6,9 +6,9 @@
|
|||
//
|
||||
|
||||
open class Target {
|
||||
public internal(set) var node: AnyNode
|
||||
public internal(set) var node: AnyView
|
||||
|
||||
public init(node: AnyNode) {
|
||||
self.node = node
|
||||
public init<V: View>(node: V) {
|
||||
self.node = AnyView(node)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
//
|
||||
// Unique.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 06/11/2018.
|
||||
//
|
||||
|
||||
/// Typealias for closures returning no value and wrapped with `Unique`.
|
||||
public typealias Handler<T> = Unique<(T) -> ()>
|
||||
|
||||
/// Classes have identity even when they have no content. `UniqueReference`
|
||||
/// defines `Equatable` as an identity comparison.
|
||||
class UniqueReference {}
|
||||
|
||||
extension UniqueReference: Equatable {
|
||||
public static func ==(lhs: UniqueReference, rhs: UniqueReference) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
}
|
||||
|
||||
/** `Unique` works around the fact that `ObjectIdentifier` can't take
|
||||
closures as arguments despite closures being reference types and having
|
||||
identity. You can pass any value or reference to `Unique.init`, which will
|
||||
create a new identity. `Unique` implements `Equatable`, but will return
|
||||
`false` on equality comparison of different identities (including closures)
|
||||
Example:
|
||||
|
||||
```swift
|
||||
let x = Unique(5)
|
||||
let y = Unique(5)
|
||||
let z = x
|
||||
|
||||
x == y // is `false`
|
||||
x == z // is `true`
|
||||
|
||||
let closure1 = Unique { 5 }
|
||||
let closure2 = Unique { 5 }
|
||||
let closure3 = closure1
|
||||
|
||||
closure1 == closure2 // is `false`
|
||||
closure3 == closure1 // is `true`
|
||||
```
|
||||
*/
|
||||
public struct Unique<T> {
|
||||
private let id = UniqueReference()
|
||||
|
||||
/// Unpacked value stored within `Unique` container.
|
||||
public let value: T
|
||||
|
||||
/// Create a new `Unique` container.
|
||||
public init(_ value: T) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
extension Unique: Equatable {
|
||||
public static func ==(lhs: Unique<T>, rhs: Unique<T>) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
//
|
||||
// View.swift
|
||||
//
|
||||
//
|
||||
// Created by Max Desiatov on 07/04/2020.
|
||||
//
|
||||
|
||||
public protocol View {
|
||||
associatedtype Body: View
|
||||
|
||||
var body: Self.Body { get }
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// Created by Max Desiatov on 08/04/2020.
|
||||
//
|
||||
|
||||
import Runtime
|
||||
|
||||
public struct AnyView: View {
|
||||
let type: Any.Type
|
||||
let typeConstructorName: String
|
||||
let bodyType: Any.Type
|
||||
var view: Any
|
||||
|
||||
// needs to take a fresh version of `view` as an argument, otherwise it captures the old view value
|
||||
let bodyClosure: (Any) -> AnyView
|
||||
|
||||
public init<V>(_ view: V) where V: View {
|
||||
if let anyView = view as? AnyView {
|
||||
type = anyView.type
|
||||
typeConstructorName = anyView.typeConstructorName
|
||||
bodyType = anyView.bodyType
|
||||
self.view = anyView.view
|
||||
bodyClosure = anyView.bodyClosure
|
||||
} else {
|
||||
type = V.self
|
||||
|
||||
// FIXME: no idea if using `mangledName` is reliable, but seems to be the only way to get
|
||||
// a name of a type constructor in runtime. Should definitely check if these are different
|
||||
// across modules, otherwise can cause problems with views with same names in different
|
||||
// modules.
|
||||
|
||||
// swiftlint:disable:next force_try
|
||||
typeConstructorName = try! typeInfo(of: type).mangledName
|
||||
|
||||
bodyType = V.Body.self
|
||||
self.view = view
|
||||
// swiftlint:disable:next force_cast
|
||||
bodyClosure = { AnyView(($0 as! V).body) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyView: ParentView {
|
||||
var children: [AnyView] {
|
||||
(view as? ParentView)?.children ?? []
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// Button.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Max Desiatov on 02/12/2018.
|
||||
//
|
||||
|
||||
public struct Button<Label>: View where Label: View {
|
||||
// FIXME: should be internal
|
||||
public let label: Label
|
||||
public let action: () -> ()
|
||||
|
||||
public init(action: @escaping () -> (), @ViewBuilder label: () -> Label) {
|
||||
self.label = label()
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
extension Button where Label == Text {
|
||||
public init<S>(_ title: S, action: @escaping () -> ()) where S: StringProtocol {
|
||||
self.init(action: action) {
|
||||
Text(title)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Created by Max Desiatov on 08/04/2020.
|
||||
//
|
||||
|
||||
public enum VerticalAlignment: Equatable {
|
||||
case top
|
||||
case center
|
||||
case bottom
|
||||
}
|
||||
|
||||
public struct HStack<Content>: View where Content: View {
|
||||
// FIXME: should be internal
|
||||
public let alignment: VerticalAlignment
|
||||
public let spacing: CGFloat?
|
||||
public let content: Content
|
||||
|
||||
public init(alignment: VerticalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) {
|
||||
self.alignment = alignment
|
||||
self.spacing = spacing
|
||||
self.content = content()
|
||||
}
|
||||
}
|
||||
|
||||
extension HStack: ParentView {
|
||||
var children: [AnyView] {
|
||||
(content as? GroupView)?.children ?? [AnyView(content)]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Created by Max Desiatov on 08/04/2020.
|
||||
//
|
||||
|
||||
public struct Text: View {
|
||||
// FIXME: should be internal
|
||||
public let content: String
|
||||
|
||||
public init(verbatim content: String) {
|
||||
self.content = content
|
||||
}
|
||||
|
||||
public init<S>(_ content: S) where S: StringProtocol {
|
||||
self.content = String(content)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// Created by Max Desiatov on 08/04/2020.
|
||||
//
|
||||
|
||||
public struct TupleView<T>: View {
|
||||
public let value: T
|
||||
|
||||
let children: [AnyView]
|
||||
|
||||
public init(_ value: T) {
|
||||
self.value = value
|
||||
children = []
|
||||
}
|
||||
|
||||
init<T1: View, T2: View>(_ v1: T1, _ v2: T2) where T == (T1, T2) {
|
||||
value = (v1, v2)
|
||||
children = [AnyView(v1), AnyView(v2)]
|
||||
}
|
||||
}
|
||||
|
||||
extension TupleView: GroupView {}
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// View.swift
|
||||
//
|
||||
//
|
||||
// Created by Max Desiatov on 07/04/2020.
|
||||
//
|
||||
|
||||
public protocol View {
|
||||
associatedtype Body: View
|
||||
|
||||
var body: Self.Body { get }
|
||||
}
|
||||
|
||||
extension Never: View {
|
||||
public typealias Body = Never
|
||||
}
|
||||
|
||||
public extension View where Body == Never {
|
||||
var body: Never { fatalError() }
|
||||
}
|
||||
|
||||
/// A `View` type that renders with subviews, usually specified in the `Content` type argument
|
||||
protocol ParentView {
|
||||
var children: [AnyView] { get }
|
||||
}
|
||||
|
||||
/// A `View` type that is not rendered, but "flattened" rendering all its children instead.
|
||||
protocol GroupView: ParentView {}
|
|
@ -0,0 +1,98 @@
|
|||
//
|
||||
// Created by Max Desiatov on 08/04/2020.
|
||||
//
|
||||
|
||||
public struct EmptyView: View {
|
||||
@inlinable public init() {}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next type_name
|
||||
public enum _ConditionalContent<TrueBranch, FalseBranch>: View
|
||||
where TrueBranch: View, FalseBranch: View {
|
||||
case trueBranch(TrueBranch)
|
||||
case falseBranch(FalseBranch)
|
||||
}
|
||||
|
||||
@_functionBuilder public struct ViewBuilder {
|
||||
public static func buildBlock() -> EmptyView { EmptyView() }
|
||||
|
||||
public static func buildBlock<Content>(
|
||||
_ content: Content
|
||||
) -> Content where Content: View {
|
||||
content
|
||||
}
|
||||
|
||||
public static func buildIf<Content>(_ content: Content?) -> Content? where Content: View {
|
||||
content
|
||||
}
|
||||
|
||||
public static func buildEither<TrueContent, FalseContent>(
|
||||
first: TrueContent
|
||||
) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent: View, FalseContent: View {
|
||||
.trueBranch(first)
|
||||
}
|
||||
|
||||
public static func buildEither<TrueContent, FalseContent>(
|
||||
second: FalseContent
|
||||
) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent: View, FalseContent: View {
|
||||
.falseBranch(second)
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable line_length
|
||||
// swiftlint:disable large_tuple
|
||||
// swiftlint:disable function_parameter_count
|
||||
|
||||
extension ViewBuilder {
|
||||
public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0: View, C1: View {
|
||||
TupleView(c0, c1)
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewBuilder {
|
||||
public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> TupleView<(C0, C1, C2)> where C0: View, C1: View, C2: View {
|
||||
TupleView((c0, c1, c2))
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewBuilder {
|
||||
public static func buildBlock<C0, C1, C2, C3>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3) -> TupleView<(C0, C1, C2, C3)> where C0: View, C1: View, C2: View, C3: View {
|
||||
TupleView((c0, c1, c2, c3))
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewBuilder {
|
||||
public static func buildBlock<C0, C1, C2, C3, C4>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4) -> TupleView<(C0, C1, C2, C3, C4)> where C0: View, C1: View, C2: View, C3: View, C4: View {
|
||||
TupleView((c0, c1, c2, c3, c4))
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewBuilder {
|
||||
public static func buildBlock<C0, C1, C2, C3, C4, C5>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5) -> TupleView<(C0, C1, C2, C3, C4, C5)> where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View {
|
||||
TupleView((c0, c1, c2, c3, c4, c5))
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewBuilder {
|
||||
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6) -> TupleView<(C0, C1, C2, C3, C4, C5, C6)> where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View {
|
||||
TupleView((c0, c1, c2, c3, c4, c5, c6))
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewBuilder {
|
||||
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7)> where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View {
|
||||
TupleView((c0, c1, c2, c3, c4, c5, c6, c7))
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewBuilder {
|
||||
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8)> where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View {
|
||||
TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8))
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewBuilder {
|
||||
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View {
|
||||
TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9))
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
//
|
||||
// AppKitRenderer.swift
|
||||
// TokamakAppKit
|
||||
//
|
||||
// Created by Max Desiatov on 02/12/2018.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
// FIXME: working around "Couldn't lookup symbols: protocol witness table"
|
||||
// compiler bug
|
||||
// let _modalPresenterWitnessTableHack: NSHostComponent.Type =
|
||||
// ModalPresenter.self
|
||||
// let _stackControllerWitnessTableHack: NSHostComponent.Type =
|
||||
// NavigationController.self
|
||||
// let _navigationItemWitnessTableHack: NSHostComponent.Type =
|
||||
// NavigationItem.self
|
||||
// let _listViewWitnessTableHack: NSHostComponent.Type =
|
||||
// ListView<HackyProvider>.self
|
||||
// let _collectionViewWitnessTableHack: NSHostComponent.Type =
|
||||
// CollectionView<HackyProvider>.self
|
||||
|
||||
struct HackyProvider: CellProvider {
|
||||
static func cell(
|
||||
props: Props, item: Int, path: CellPath
|
||||
) -> AnyNode {
|
||||
return Null.node()
|
||||
}
|
||||
|
||||
typealias Props = Null
|
||||
typealias Model = Int
|
||||
}
|
||||
|
||||
class NSTarget: Target {
|
||||
var viewController: NSViewController {
|
||||
fatalError("\(#function) should be overriden in NSTarget subclass")
|
||||
}
|
||||
|
||||
var refTarget: Any {
|
||||
fatalError("\(#function) should be overriden in NSTarget subclass")
|
||||
}
|
||||
}
|
||||
|
||||
/// UIKitRenderer is an implementation of `Renderer` with UIKit as a target.
|
||||
final class AppKitRenderer: Renderer {
|
||||
private(set) var reconciler: StackReconciler<AppKitRenderer>?
|
||||
private weak var rootViewController: NSViewController!
|
||||
|
||||
init(_ node: AnyNode, rootViewController: NSViewController) {
|
||||
self.rootViewController = rootViewController
|
||||
reconciler = StackReconciler(
|
||||
node: node,
|
||||
target: ViewBox(rootViewController.view, rootViewController, node),
|
||||
renderer: self
|
||||
)
|
||||
}
|
||||
|
||||
private func typeAssertionFailure(for type: AnyHostComponent.Type) {
|
||||
assertionFailure("""
|
||||
component type \(type) not supported by AppKitRenderer
|
||||
""")
|
||||
}
|
||||
|
||||
func mountTarget(
|
||||
to parent: NSTarget,
|
||||
with component: AppKitRenderer.MountedHost
|
||||
) -> NSTarget? {
|
||||
guard let rendererComponent = component.type as? NSHostComponent.Type else {
|
||||
typeAssertionFailure(for: component.type)
|
||||
return nil
|
||||
}
|
||||
|
||||
return rendererComponent.mountTarget(to: parent,
|
||||
component: component,
|
||||
self)
|
||||
}
|
||||
|
||||
func update(
|
||||
target: NSTarget,
|
||||
with component: AppKitRenderer.MountedHost
|
||||
) {
|
||||
guard let rendererComponent = component.type as? NSHostComponent.Type else {
|
||||
typeAssertionFailure(for: component.type)
|
||||
return
|
||||
}
|
||||
|
||||
rendererComponent.update(target: target,
|
||||
node: component.node)
|
||||
|
||||
guard
|
||||
let componentType = component.type as? AnyRefComponent.Type,
|
||||
let anyRef = component.node.ref else { return }
|
||||
|
||||
componentType.update(ref: anyRef, with: target.refTarget)
|
||||
}
|
||||
|
||||
func unmount(
|
||||
target: NSTarget,
|
||||
from parent: NSTarget,
|
||||
with component: AppKitRenderer.MountedHost,
|
||||
completion: @escaping () -> ()
|
||||
) {
|
||||
guard let rendererComponent = component.type as? NSHostComponent.Type else {
|
||||
typeAssertionFailure(for: component.type)
|
||||
return
|
||||
}
|
||||
|
||||
rendererComponent.unmount(target: target, completion: completion)
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
//
|
||||
// ContainerViewController.swift
|
||||
// TokamakUIKit
|
||||
//
|
||||
// Created by Max Desiatov on 01/01/2019.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
final class ContainerViewController: NSViewController {
|
||||
private let contained: NSView
|
||||
|
||||
init(contained: NSView) {
|
||||
self.contained = contained
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
view.addSubview(contained)
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
//
|
||||
// ControlBox.swift
|
||||
// TokamakAppKit
|
||||
//
|
||||
// Created by Max Desiatov on 01/01/2019.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
final class Action {
|
||||
private let handler: (()) -> ()
|
||||
|
||||
init(_ handler: @escaping (()) -> ()) {
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
@objc func perform() {
|
||||
handler(())
|
||||
}
|
||||
}
|
||||
|
||||
let actionSelector = #selector(Action.perform)
|
||||
|
||||
/// Wraps Objective-C target/action pattern used by `UIControl` with a swifty
|
||||
/// closure-based API.
|
||||
class ControlBox<T: NSControl & Default>: ViewBox<T> {
|
||||
private var handlersMask: NSEvent.EventTypeMask = []
|
||||
private var handlers = [Event: Handler<()>]()
|
||||
private var action: Action!
|
||||
|
||||
// this delegate stays as a constant and doesn't create a reference cycle
|
||||
// swiftlint:disable:next weak_delegate
|
||||
let delegate = TextEditingDelegate()
|
||||
|
||||
override init(
|
||||
_ view: T,
|
||||
_ viewController: NSViewController,
|
||||
_ node: AnyNode
|
||||
) {
|
||||
super.init(view, viewController, node)
|
||||
|
||||
delegate.callback = { [weak self] in
|
||||
self?.handlers[$0]?.value(())
|
||||
}
|
||||
|
||||
action = Action { [weak self] in
|
||||
guard let eventMask = NSApp.currentEvent?.associatedEventsMask else {
|
||||
return
|
||||
}
|
||||
|
||||
let events = eventMask.elements().compactMap { Event($0) }
|
||||
|
||||
for e in events {
|
||||
self?.handlers[e]?.value(())
|
||||
}
|
||||
}
|
||||
view.target = action
|
||||
view.action = actionSelector
|
||||
}
|
||||
|
||||
func bind(handlers: [Event: Handler<()>]) {
|
||||
self.handlers = handlers
|
||||
}
|
||||
}
|
||||
|
||||
/// `OptionSet` enumeration for easy conversion from `EventTypeMask` to `Event`
|
||||
extension OptionSet where RawValue: FixedWidthInteger {
|
||||
func elements() -> AnySequence<Self> {
|
||||
var remainingBits = rawValue
|
||||
var bitMask: RawValue = 1
|
||||
return AnySequence {
|
||||
AnyIterator {
|
||||
while remainingBits != 0 {
|
||||
defer { bitMask = bitMask &* 2 }
|
||||
if remainingBits & bitMask != 0 {
|
||||
remainingBits = remainingBits & ~bitMask
|
||||
return Self(rawValue: bitMask)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper class that work around the fact that `ControlBox` isn't a subclass
|
||||
of `NSObject`, which is required by `NSControlTextEditingDelegate`
|
||||
*/
|
||||
class TextEditingDelegate: NSObject, NSControlTextEditingDelegate {
|
||||
fileprivate var callback: ((Event) -> ())?
|
||||
|
||||
func controlTextDidBeginEditing(_: Notification) {
|
||||
callback?(.editingDidBegin)
|
||||
}
|
||||
|
||||
func controlTextDidChange(_ obj: Notification) {
|
||||
callback?(.editingChanged)
|
||||
}
|
||||
|
||||
func controlTextDidEndEditing(_ obj: Notification) {
|
||||
callback?(.editingDidEnd)
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// ViewBox.swift
|
||||
// TokamakAppKit
|
||||
//
|
||||
// Created by Max Desiatov on 29/12/2018.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
class ViewBox<T: NSView>: ViewControllerBox<NSViewController> {
|
||||
let view: T
|
||||
|
||||
/// Array of constraints installed from props that configured this view
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
|
||||
init(_ view: T, _ viewController: NSViewController, _ node: AnyNode) {
|
||||
self.view = view
|
||||
|
||||
super.init(viewController, node)
|
||||
}
|
||||
|
||||
override var refTarget: Any {
|
||||
return view
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// ViewControllerBox.swift
|
||||
// TokamakAppKit
|
||||
//
|
||||
// Created by Max Desiatov on 11/01/2019.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
class ViewControllerBox<T: NSViewController>: NSTarget {
|
||||
let containerViewController: T
|
||||
|
||||
init(_ viewController: T, _ node: AnyNode) {
|
||||
containerViewController = viewController
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
override var viewController: NSViewController {
|
||||
return containerViewController
|
||||
}
|
||||
|
||||
override var refTarget: Any {
|
||||
return containerViewController
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
//
|
||||
// Button.swift
|
||||
// TokamakUIKit
|
||||
//
|
||||
// Created by Max Desiatov on 29/12/2018.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
final class TokamakButton: NSButton, Default {
|
||||
/// This property can't be defined in a `UIButton` extension
|
||||
/// as a plain `UIView` needs a different implementation of `defaultValue`
|
||||
/// and subclass extensions can't override extensions of a parent class.
|
||||
static var defaultValue: TokamakButton {
|
||||
let result = TokamakButton()
|
||||
result.bezelStyle = .rounded
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension Button: NSControlComponent {
|
||||
typealias Target = TokamakButton
|
||||
public typealias RefTarget = NSButton
|
||||
|
||||
static func update(control box: ControlBox<TokamakButton>,
|
||||
_ props: Button.Props,
|
||||
_ children: [AnyNode]) {
|
||||
let control = box.view
|
||||
|
||||
control.contentTintColor = props.titleColor.flatMap { NSColor($0) }
|
||||
|
||||
control.title = props.text
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// Label.swift
|
||||
// TokamakUIKit
|
||||
//
|
||||
// Created by Max Desiatov on 29/12/2018.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
final class TokamakLabel: NSTextView, Default {
|
||||
static var defaultValue: TokamakLabel {
|
||||
let result = TokamakLabel()
|
||||
result.isEditable = false
|
||||
result.backgroundColor = .clear
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension NSTextAlignment {
|
||||
public init(_ alignment: TextAlignment) {
|
||||
switch alignment {
|
||||
case .left:
|
||||
self = .left
|
||||
case .right:
|
||||
self = .right
|
||||
case .center:
|
||||
self = .center
|
||||
case .justified:
|
||||
self = .justified
|
||||
case .natural:
|
||||
self = .natural
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Label: NSViewComponent {
|
||||
public typealias RefTarget = NSTextView
|
||||
|
||||
static func update(view box: ViewBox<TokamakLabel>,
|
||||
_ props: Label.Props,
|
||||
_ children: [AnyNode]) {
|
||||
let view = box.view
|
||||
view.alignment = NSTextAlignment(props.alignment)
|
||||
view.textContainer?.maximumNumberOfLines = props.numberOfLines
|
||||
view.textContainer?.lineBreakMode = NSLineBreakMode(props.lineBreakMode)
|
||||
if let textColor = props.textColor {
|
||||
view.textColor = NSColor(textColor)
|
||||
} else {
|
||||
view.textColor = .textColor
|
||||
}
|
||||
view.string = props.text
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
//
|
||||
// StackView.swift
|
||||
// TokamakUIKit
|
||||
//
|
||||
// Created by Max Desiatov on 29/12/2018.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
final class TokamakStackView: NSStackView, Default {
|
||||
static var defaultValue: TokamakStackView {
|
||||
return TokamakStackView()
|
||||
}
|
||||
}
|
||||
|
||||
extension NSLayoutConstraint.Attribute {
|
||||
public init?(_ alignment: StackView.Props.Alignment) {
|
||||
switch alignment {
|
||||
case .top:
|
||||
self = .top
|
||||
case .bottom:
|
||||
self = .bottom
|
||||
case .leading:
|
||||
self = .leading
|
||||
case .trailing:
|
||||
self = .trailing
|
||||
case .fill, .center:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSUserInterfaceLayoutOrientation {
|
||||
public init(_ axis: StackView.Props.Axis) {
|
||||
switch axis {
|
||||
case .horizontal:
|
||||
self = .horizontal
|
||||
case .vertical:
|
||||
self = .vertical
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSStackView.Distribution {
|
||||
public init(_ distribution: StackView.Props.Distribution) {
|
||||
switch distribution {
|
||||
case .fill:
|
||||
self = .fill
|
||||
case .fillEqually:
|
||||
self = .fillEqually
|
||||
case .fillProportionally:
|
||||
self = .fillProportionally
|
||||
case .equalSpacing:
|
||||
self = .equalSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension StackView: NSViewComponent {
|
||||
public typealias RefTarget = NSStackView
|
||||
|
||||
static func update(view box: ViewBox<TokamakStackView>,
|
||||
_ props: StackView.Props,
|
||||
_: [AnyNode]) {
|
||||
let view = box.view
|
||||
NSLayoutConstraint.Attribute(props.alignment).flatMap {
|
||||
view.alignment = $0
|
||||
}
|
||||
view.orientation = NSUserInterfaceLayoutOrientation(props.axis)
|
||||
view.distribution = NSStackView.Distribution(props.distribution)
|
||||
view.spacing = CGFloat(props.spacing)
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
//
|
||||
// View.swift
|
||||
// TokamakUIKit
|
||||
//
|
||||
// Created by Max Desiatov on 31/12/2018.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
final class TokamakView: NSView, Default {
|
||||
public static var defaultValue: TokamakView {
|
||||
return TokamakView()
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewComponent: NSViewComponent {
|
||||
public typealias RefTarget = NSView
|
||||
|
||||
static func update(view: ViewBox<TokamakView>,
|
||||
_ props: ViewComponent.Props,
|
||||
_: [AnyNode]) {}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// Color.swift
|
||||
// TokamakAppKit
|
||||
//
|
||||
// Created by Max Desiatov on 14/12/2018.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
extension NSColor {
|
||||
public convenience init(_ color: Color) {
|
||||
switch color.space {
|
||||
case .sRGB:
|
||||
self.init(red: CGFloat(color.red),
|
||||
green: CGFloat(color.green),
|
||||
blue: CGFloat(color.blue),
|
||||
alpha: CGFloat(color.alpha))
|
||||
case .displayP3:
|
||||
self.init(displayP3Red: CGFloat(color.red),
|
||||
green: CGFloat(color.green),
|
||||
blue: CGFloat(color.blue),
|
||||
alpha: CGFloat(color.alpha))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// BaselineConstraint.swift
|
||||
// TokamakAppKit
|
||||
//
|
||||
// Created by Max Desiatov on 03/02/2019.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Tokamak
|
||||
|
||||
protocol BaselineConstraint {
|
||||
var firstAnchor: KeyPath<NSView, NSLayoutYAxisAnchor> { get }
|
||||
var secondAnchor: KeyPath<NSView, NSLayoutYAxisAnchor> { get }
|
||||
var target: Constraint.Target { get }
|
||||
var constant: Double { get }
|
||||
}
|
||||
|
||||
extension BaselineConstraint {
|
||||
func constraint(
|
||||
current: NSView,
|
||||
parent: NSView?,
|
||||
next: NSView?
|
||||
) -> [NSLayoutConstraint] {
|
||||
let secondView: NSView?
|
||||
switch target {
|
||||
case .next:
|
||||
secondView = next
|
||||
case .parent:
|
||||
secondView = parent
|
||||
}
|
||||
|
||||
guard let second = secondView?[keyPath: secondAnchor] else { return [] }
|
||||
|
||||
return [current[keyPath: firstAnchor].constraint(
|
||||
equalTo: second,
|
||||
constant: CGFloat(constant)
|
||||
)]
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue