├── .swift-version
├── Sources
├── HolyCow
│ └── main.swift
└── SwiftWebUI
│ ├── Misc
│ ├── FakeCompactImplementations.swift
│ └── DebugSwitches.swift
│ ├── Values
│ ├── ContentMode.swift
│ ├── UserInterfaceSizeClass.swift
│ ├── StaticMember.swift
│ ├── Shadow.swift
│ ├── ContentSizeCategory.swift
│ ├── NavigationBarItem.swift
│ ├── EditMode.swift
│ ├── LocalizedStringKey.swift
│ ├── ImagePaint.swift
│ ├── VerticalAlignment.swift
│ ├── HorizontalAlignment.swift
│ ├── EdgeInsets.swift
│ ├── Length.swift
│ ├── Alignment.swift
│ ├── Color.swift
│ ├── Font.swift
│ └── Formatter.swift
│ ├── Views
│ ├── Shapes
│ │ ├── README.md
│ │ ├── Shape.swift
│ │ ├── ShapeView.swift
│ │ ├── ShapeStyle.swift
│ │ ├── Rectangle.swift
│ │ ├── RoundedRectangle.swift
│ │ ├── SizedShape.swift
│ │ ├── Path.swift
│ │ └── UXKit.swift
│ ├── View.swift
│ ├── Navigation
│ │ ├── NavigationSidebar.swift
│ │ ├── NavigationContent.swift
│ │ ├── NavigationContext.swift
│ │ ├── NavigationLink.swift
│ │ └── NavigationView.swift
│ ├── Images
│ │ ├── ColorView.swift
│ │ ├── CGImage.swift
│ │ ├── SystemImage.swift
│ │ └── Image.swift
│ ├── Generic
│ │ ├── DynamicViewContent.swift
│ │ ├── NeverView.swift
│ │ ├── EmptyView.swift
│ │ ├── Group.swift
│ │ ├── ForEach.swift
│ │ ├── ConditionalContent.swift
│ │ ├── AnyView.swift
│ │ └── TapAction.swift
│ ├── Forms
│ │ ├── SecureField.swift
│ │ ├── Form.swift
│ │ ├── SUIButtonStyle.swift
│ │ ├── SUIToggleStyle.swift
│ │ ├── SUITextFieldStyle.swift
│ │ ├── Stepper.swift
│ │ ├── HTMLPopUpPickerStyle.swift
│ │ └── HTMLRadioPickerStyle.swift
│ ├── SemanticUI
│ │ ├── SUIFlag.swift
│ │ ├── SUISegment.swift
│ │ ├── SUILabel.swift
│ │ └── SUICard.swift
│ ├── Layout
│ │ ├── ScrollView.swift
│ │ ├── Divider.swift
│ │ ├── Spacer.swift
│ │ ├── HStack.swift
│ │ ├── VStack.swift
│ │ ├── SwitchView.swift
│ │ └── Section.swift
│ ├── TabbedView
│ │ ├── TabItemLabel.swift
│ │ └── SUITabView.swift
│ ├── HTML
│ │ ├── HTML.swift
│ │ └── HTMLContainer.swift
│ └── Unsplash
│ │ ├── Unsplash.swift
│ │ └── UnsplashSource.swift
│ ├── Modifiers
│ ├── ViewTag.swift
│ ├── TraitWritingModifier.swift
│ ├── ResizableModifier.swift
│ ├── VisibilityModifiers.swift
│ ├── ShadowModifier.swift
│ ├── ViewModifier.swift
│ ├── EnvironmentObjectWritingModifier.swift
│ ├── EnvironmentKeyWritingModifier.swift
│ ├── FrameModifier.swift
│ ├── BackgroundModifier.swift
│ ├── PaddingModifier.swift
│ ├── RelativeSizeModifiers.swift
│ └── ModifiedContent.swift
│ ├── WASMHosting
│ ├── DispatchQueuePolyfill.swift
│ ├── JSONPolyfill.swift
│ └── NetworkingPolyfill.swift
│ ├── ViewHosting
│ ├── NIOSessionIDGeneration.swift
│ ├── HTMLStringHelpers.swift
│ ├── CSSStyles.swift
│ ├── Serve.swift
│ └── HTMLChange.swift
│ ├── VirtualDOM
│ ├── HTMLWrappingActionNode.swift
│ ├── Generic
│ │ ├── GroupNode.swift
│ │ ├── NotImplementedViewNode.swift
│ │ ├── EmptyNode.swift
│ │ ├── HTMLSwitchNode.swift
│ │ ├── ResolvedConditionNode.swift
│ │ └── HTMLClickContainerNode.swift
│ ├── HTMLLeafNode.swift
│ ├── Images
│ │ ├── CGImageNode.swift
│ │ └── HTMLColorNode.swift
│ ├── Layout
│ │ ├── HTMLScrollNode.swift
│ │ ├── FlexBoxSpacerNode.swift
│ │ ├── HTMLShadowNode.swift
│ │ ├── HTMLFrameNode.swift
│ │ ├── HTMLBackgroundNode.swift
│ │ ├── SUIListNode.swift
│ │ ├── SUIListItemNode.swift
│ │ └── HTMLPaddingNode.swift
│ ├── HTML
│ │ └── HTMLOutputNode.swift
│ ├── HTMLWrappingNode.swift
│ ├── Forms
│ │ ├── SUIButtonNode.swift
│ │ ├── HTMLSelectNode.swift
│ │ ├── HTMLOptionNode.swift
│ │ ├── HTMLRadioNode.swift
│ │ └── SUIInputNode.swift
│ ├── TreeBuilding
│ │ └── SUIListBuilder.swift
│ └── TabbedView
│ │ ├── SUITabItemNode.swift
│ │ ├── SUITabSegmentNode.swift
│ │ └── SUITabContainerNode.swift
│ ├── Properties
│ ├── ObservableObject.swift
│ ├── BindingConvertible.swift
│ ├── Identifiable.swift
│ ├── SelectionManager.swift
│ ├── DynamicViewProperty.swift
│ ├── State.swift
│ ├── EnvironmentObject.swift
│ └── ObservedObject.swift
│ └── Environment
│ ├── Environment.swift
│ └── EnvironmentValues.swift
├── docs
└── wasmgif.gif
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── CONTRIBUTING.md
├── .travis.d
├── before-install.sh
└── install.sh
├── .travis.yml
├── Makefile
├── Package.swift
├── .gitignore
└── README.md
/.swift-version:
--------------------------------------------------------------------------------
1 | wasm-DEVELOPMENT-SNAPSHOT-2020-05-24-a
2 |
--------------------------------------------------------------------------------
/Sources/HolyCow/main.swift:
--------------------------------------------------------------------------------
1 | import SwiftWebUI
2 |
3 | SwiftWebUI.serve(Text("Holy Cow!"))
4 |
--------------------------------------------------------------------------------
/docs/wasmgif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carson-katri/SwiftWebUI/HEAD/docs/wasmgif.gif
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Legal
2 |
3 | By submitting a pull request, you represent that you have the right to license
4 | your contribution to ZeeZide and the community, and agree by submitting the patch
5 | that your contributions are licensed under the Apache 2.0 license.
6 |
7 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Misc/FakeCompactImplementations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FakeCompactImplementations.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 05.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | protocol PreviewProvider {}
10 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/ContentMode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentMode.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 10.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public enum ContentMode: Hashable {
10 | case fit
11 | case fill
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Shapes/README.md:
--------------------------------------------------------------------------------
1 | # Shapes
2 |
3 | Not quite sure what to do with those.
4 |
5 | Some shapes can be represented in CSS. Probably all in SVG. Need to figure out
6 | what one wants to happen.
7 |
8 | The shapes we support could generate a
for now? E.g. a Rectangle/RoundedRect.
9 |
10 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/UserInterfaceSizeClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserInterfaceSizeClass.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 22.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public enum UserInterfaceSizeClass: Hashable {
10 | case compact, regular
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 05.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public protocol View {
10 |
11 | associatedtype Body : View
12 |
13 | var body : Self.Body { get }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/StaticMember.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StaticMember.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 18.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct StaticMember
{
10 | // This is used for stuff like ListStyle
11 | public var base: Base
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/Shadow.swift:
--------------------------------------------------------------------------------
1 | //
2 |
3 | public struct Shadow: Hashable {
4 | public var color: Color, radius: Length, x: Length, y: Length
5 | }
6 |
7 | extension Shadow: CSSStyleValue {
8 |
9 | public var cssStringValue: String {
10 | return "\(radius.cssStringValue) \(x.cssStringValue) \(y.cssStringValue) \(color.cssStringValue)"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/ContentSizeCategory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentSizeCategory.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 10.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public enum ContentSizeCategory: Equatable, Hashable {
10 | case medium
11 | case extraSmall, small
12 | case large, extraLarge, extraExtraLarge, extraExtraExtraLarge
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Shapes/Shape.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Shape.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 24.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public protocol Shape: View, Equatable {
10 | func path(in rect: UXRect) -> Path
11 | }
12 |
13 | public protocol InsettableShape: Shape {
14 | associatedtype InsetShape : Shape
15 | func inset(by amount: Length) -> InsetShape
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Navigation/NavigationSidebar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationSidebar.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 22.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | struct NavigationSidebar
: View {
10 |
11 | let root : Root
12 |
13 | var body: some View {
14 | HTMLContainer(classes: [ "swiftui-nav-sidebar" ]) {
15 | root
16 | }
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/NavigationBarItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationBarItem.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 23.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct NavigationBarItem {
10 | public enum TitleDisplayMode: Hashable {
11 | case automatic, inline, large
12 | }
13 |
14 | let view : AnyView
15 | let displayMode : NavigationBarItem.TitleDisplayMode
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/ViewTag.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewTag.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 18.06.19.
6 | // Copyright © 2019-2020 Helge Heß. All rights reserved.
7 | //
8 |
9 | public extension View {
10 |
11 | func tag(_ tag: V) -> some View {
12 | return modifier(TraitWritingModifier(content: ViewTag(value: tag)))
13 | }
14 | }
15 |
16 | public struct ViewTag: Trait {
17 | let value : Value
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/EditMode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EditMode.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 22.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public enum EditMode: Hashable {
10 |
11 | case inactive, transient, active
12 |
13 | public var isEditing: Bool {
14 | switch self {
15 | case .inactive: return false
16 | case .transient, .active: return true
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/WASMHosting/DispatchQueuePolyfill.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchQueuePolyfill.swift
3 | //
4 | //
5 | // Created by Carson Katri on 5/29/20.
6 | //
7 |
8 | public class DispatchQueue {
9 | public static let main = DispatchQueue()
10 |
11 | public func async(execute: () -> Void) {
12 | execute()
13 | }
14 | }
15 |
16 | public extension Error {
17 | var localizedDescription: String {
18 | String(describing: self)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Images/ColorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorView.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 17.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | extension Color: View {
10 | public typealias Body = Never
11 |
12 | }
13 |
14 | extension Color: TreeBuildingView {
15 |
16 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
17 | return HTMLColorNode(elementID: context.currentElementID, color: self)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/TraitWritingModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TraitWritingModifier.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 18.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct TraitWritingModifier: ViewModifier {
10 |
11 | let content : Content
12 |
13 | public func push(to context: TreeStateContext) {
14 | context.setTrait(content)
15 | }
16 | public func pop(from context: TreeStateContext) {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Generic/DynamicViewContent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicViewContent.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 11.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public protocol DynamicViewContent : View {
10 | // repeatable content
11 |
12 | associatedtype Data : Collection
13 |
14 | var data: Self.Data { get }
15 | }
16 |
17 | // Note: This has stuff like onDelete, onMove, onInsert,
18 | // which return `_TraitWritingModifier`s.
19 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Images/CGImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGImage.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 22.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | #if canImport(CoreGraphics)
10 |
11 | import class CoreGraphics.CGImage
12 |
13 | public extension Image {
14 |
15 | init(_ cgImage: CGImage, scale: Length? = nil, label: Text? = nil) {
16 | self.storage = .cgImage(cgImage, scale: scale, label: label)
17 | }
18 |
19 | }
20 |
21 | #endif // canImport(CoreGraphics)
22 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/LocalizedStringKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocalizedStringKey.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 20.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct LocalizedStringKey: Equatable, ExpressibleByStringInterpolation {
10 |
11 | let value : String
12 |
13 | public init(_ value: String) {
14 | self.value = value
15 | }
16 | public init(stringLiteral value: String) {
17 | self.value = value
18 | }
19 |
20 | // support the interpolation stuff
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/ViewHosting/NIOSessionIDGeneration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NIOSessionIDGeneration.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 19.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | #if canImport(Foundation) && canImport(NIO)
10 | import struct Foundation.UUID
11 |
12 | extension NIOHostingSession {
13 | static func createSessionID() -> String {
14 | // As discussed in https://github.com/swiftwebui/SwiftWebUI/issues/4
15 | return UUID().uuidString
16 | }
17 | }
18 | #endif
19 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Forms/SecureField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SecureField.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 23.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct SecureField: View {
10 |
11 | public let body : TextField
12 |
13 | public init(_ text: Binding, placeholder: Text? = nil,
14 | onCommit: (() -> Void)? = nil)
15 | {
16 | body = TextField(secureText: text, placeholder: placeholder,
17 | onCommit: onCommit)
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/SemanticUI/SUIFlag.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SUIFlag.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 26.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct SUIFlag: View {
10 |
11 | let clazz : String
12 |
13 | public init(code: String) {
14 | clazz = code.lowercased()
15 | }
16 | public init(_ name: String) {
17 | clazz = name.lowercased()
18 | }
19 |
20 | public var body: some View {
21 | HTMLContainer("i", classes: [ clazz, "flag" ]) {
22 | EmptyView()
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Navigation/NavigationContent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationContent.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 22.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | struct NavigationContent: View {
10 |
11 | @EnvironmentObject private var navigationContext: NavigationContext
12 |
13 | var body: some View {
14 | HTMLContainer(classes: [ "swiftui-nav-content" ],
15 | styles: [ .flexGrow: 5 ])
16 | {
17 | SwitchView(navigationContext.activeTargetView)
18 | .padding(.fontSize(0.5))
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/ImagePaint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePaint.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 24.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct ImagePaint: Equatable {
10 |
11 | public var image : Image
12 | public var sourceRect : UXRect
13 | public var scale : Length
14 |
15 | public init(image: Image,
16 | sourceRect: UXRect = UXRect(x: 0, y: 0, width: 1, height: 1),
17 | scale: Length = 1)
18 | {
19 | self.image = image
20 | self.sourceRect = sourceRect
21 | self.scale = scale
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Generic/NeverView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NeverView.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 05.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | extension Never : View {
10 |
11 | public var body : Never { fatalError("no body in Never") }
12 |
13 | }
14 |
15 | extension View where Body == Never {
16 | public var body : Never { fatalError("no body in \(type(of: self))") }
17 | }
18 |
19 | extension Never: TreeBuildingView {
20 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
21 | return EmptyNode(elementID: context.currentElementID)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.travis.d/before-install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ "$TRAVIS_OS_NAME" == "Linux" ]]; then
4 | sudo apt-get install -y wget \
5 | clang-3.6 libc6-dev make git libicu52 libicu-dev \
6 | git autoconf libtool pkg-config \
7 | libblocksruntime-dev \
8 | libkqueue-dev \
9 | libpthread-workqueue-dev \
10 | systemtap-sdt-dev \
11 | libbsd-dev libbsd0 libbsd0-dbg \
12 | curl libcurl4-openssl-dev \
13 | libedit-dev \
14 | python2.7 python2.7-dev \
15 | libxml2
16 |
17 | sudo update-alternatives --quiet --install /usr/bin/clang clang /usr/bin/clang-3.6 100
18 | sudo update-alternatives --quiet --install /usr/bin/clang++ clang++ /usr/bin/clang++-3.6 100
19 | fi
20 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Generic/EmptyView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmptyView.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 05.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct EmptyView : View {
10 | public typealias Body = Never
11 | public init() {}
12 | }
13 |
14 | extension HTMLTreeBuilder {
15 | func buildTree(for view: EmptyView, in context: TreeStateContext)
16 | -> HTMLTreeNode
17 | {
18 | return EmptyNode(elementID: context.currentElementID)
19 | }
20 | }
21 |
22 | extension EmptyView: TreeBuildingView {
23 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
24 | return context.currentBuilder.buildTree(for: self, in: context)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Shapes/ShapeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShapeView.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 24.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct ShapeView: View {
10 | public typealias Body = Never
11 |
12 | public var shape : Content
13 | public var style : Style
14 | }
15 |
16 | #if false // @available(*, unavailable)
17 | extension HTMLTreeBuilder {
18 |
19 | func buildTree(for view: ShapeView,
20 | in context: TreeStateContext) -> HTMLTreeNode
21 | {
22 | // TODO: Render a DIV?
23 | return NotImplementedViewNode()
24 | }
25 |
26 | }
27 | #endif
28 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/VirtualDOM/HTMLWrappingActionNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTMLWrappingActionNode.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 23.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public protocol HTMLWrappingActionNode: HTMLWrappingNode {
10 | var isEnabled : Bool { get }
11 | var action : () -> Void { get }
12 | }
13 |
14 | extension HTMLWrappingActionNode {
15 |
16 | func invoke(_ webID: [ String ], in context: TreeStateContext) throws {
17 | guard elementID.isContainedInWebID(webID) else { return }
18 | if elementID.count == webID.count {
19 | action()
20 | }
21 | else {
22 | try content.invoke(webID, in: context)
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Shapes/ShapeStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShapeStyle.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 24.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public protocol ShapeStyle {
10 | }
11 | extension ShapeStyle {
12 | public typealias Member = StaticMember
13 | }
14 |
15 | extension Color: ShapeStyle {
16 | }
17 |
18 | extension ImagePaint: ShapeStyle {
19 |
20 | }
21 |
22 | #if false // for those??
23 | extension StaticMember where Base : ShapeStyle {
24 | public static var color : Color.Member {
25 | return Color.Member(base: Color.black)
26 | }
27 | }
28 | #endif
29 |
30 | public struct ForegroundStyle: ShapeStyle {
31 | // No idea what this is. Just a type for matching?
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Generic/Group.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Group.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 16.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct Group : View {
10 |
11 | public typealias Body = Never
12 |
13 | let content : Content
14 |
15 | public init(@ViewBuilder content: () -> Content) {
16 | self.content = content()
17 | }
18 | }
19 |
20 | extension Group: CustomStringConvertible {
21 | public var description: String { return "" }
22 | }
23 |
24 | extension Group: TreeBuildingView {
25 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
26 | return context.currentBuilder.buildTree(for: self, in: context)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.travis.d/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # our path is:
4 | # /home/travis/build/NozeIO/Noze.io/
5 |
6 | if ! test -z "$SWIFT_SNAPSHOT_NAME"; then
7 | # Install Swift
8 | wget "${SWIFT_SNAPSHOT_NAME}"
9 |
10 | TARBALL="`ls swift-*.tar.gz`"
11 | echo "Tarball: $TARBALL"
12 |
13 | TARPATH="$PWD/$TARBALL"
14 |
15 | cd $HOME # expand Swift tarball in $HOME
16 | tar zx --strip 1 --file=$TARPATH
17 | pwd
18 |
19 | export PATH="$PWD/usr/bin:$PATH"
20 | which swift
21 |
22 | if [ `which swift` ]; then
23 | echo "Installed Swift: `which swift`"
24 | else
25 | echo "Failed to install Swift?"
26 | exit 42
27 | fi
28 | fi
29 |
30 | swift --version
31 |
32 |
33 | # Environment
34 |
35 | TT_SWIFT_BINARY=`which swift`
36 |
37 | echo "${TT_SWIFT_BINARY}"
38 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Forms/Form.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Form.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 26.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct Form : View {
10 | // In our case a Form is just CSS styling, isn't it?
11 | // The CSS for this would appreciate some love ;-) Using a VStack for now.
12 |
13 | let content : VStack
14 |
15 | public init(@ViewBuilder content: () -> Content) {
16 | self.content =
17 | VStack(alignment: .stretch, content: content)
18 | }
19 |
20 | public var body: some View {
21 | return HTMLContainer("form", classes: [ "swiftui-form", "ui", "form" ]) {
22 | content
23 | .relativeWidth(1)
24 | }
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/ResizableModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ResizableModifier.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Carson Katri on 6/13/20.
6 | //
7 |
8 | public extension Image {
9 | func resizable() -> ModifiedContent {
10 | return modifier(ResizableModifier(image: self))
11 | }
12 | }
13 |
14 | public struct ResizableModifier: ViewModifier {
15 | let image: Image
16 |
17 | public func buildTree(for view: T, in context: TreeStateContext)
18 | -> HTMLTreeNode
19 | {
20 | return HTMLImageNode(elementID : context.currentElementID,
21 | storage : image.storage,
22 | scale : context.environment.imageScale,
23 | resizable : true)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/VirtualDOM/Generic/GroupNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GroupNode.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 23.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | struct GroupNode : HTMLWrappingNode {
10 |
11 | let elementID : ElementID
12 | let content : HTMLTreeNode // type erased, hm
13 |
14 | func nodeByApplyingNewContent(_ newContent: HTMLTreeNode) -> Self {
15 | return GroupNode(elementID: elementID, content: newContent)
16 | }
17 |
18 | var isContentNode : Bool { return false }
19 |
20 | public func dump(nesting: Int) {
21 | let indent = String(repeating: " ", count: nesting)
22 | print("\(indent)")
23 | content.dump(nesting: nesting + 1)
24 | print("\(indent)")
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Layout/ScrollView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollView.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 23.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct ScrollView : View, CustomStringConvertible {
10 | public typealias Body = Never
11 |
12 | // TODO: support the properties we can?
13 |
14 | let content: Content
15 |
16 | public init(@ViewBuilder content: () -> Content) {
17 | self.content = content()
18 | }
19 |
20 | public var description: String {
21 | return ""
22 | }
23 | }
24 |
25 | extension ScrollView: TreeBuildingView {
26 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
27 | return context.currentBuilder.buildTree(for: self, in: context)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: generic
2 |
3 | notifications:
4 | slack: nozeio:LIFY1Jtkx0FRcLq3u1WliHRZ
5 |
6 | matrix:
7 | include:
8 | - os: Linux
9 | dist: xenial
10 | env: SWIFT_SNAPSHOT_NAME="https://swift.org/builds/swift-5.1-release/ubuntu1604/swift-5.1-RELEASE/swift-5.1-RELEASE-ubuntu16.04.tar.gz"
11 | sudo: required
12 | - os: Linux
13 | dist: xenial
14 | env: SWIFT_SNAPSHOT_NAME="https://swift.org/builds/swift-5.2-release/ubuntu1604/swift-5.2-RELEASE/swift-5.2-RELEASE-ubuntu16.04.tar.gz"
15 | sudo: required
16 |
17 | before_install:
18 | - ./.travis.d/before-install.sh
19 |
20 | install:
21 | - ./.travis.d/install.sh
22 |
23 | script:
24 | - export PATH="$HOME/usr/bin:$PATH"
25 | - swift build -c release
26 | - swift build -c debug
27 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/VisibilityModifiers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VisibilityModifiers.swift
3 | //
4 | //
5 | // Created by Carson Katri on 5/28/20.
6 | //
7 |
8 | public extension View {
9 | func onAppear(perform action: (() -> Void)? = nil) -> some View {
10 | return modifier(OnLoadModifier(action: action ?? {}))
11 | }
12 | }
13 |
14 | struct OnLoadModifier: ViewModifier {
15 | let action: () -> Void
16 |
17 | func buildTree(for view: T, in context: TreeStateContext) -> HTMLTreeNode {
18 | // This is hacked in pretty poorly, but it works.
19 | // We really need a way to track this per-View.
20 | // Not sure how to go about that yet.
21 | if context.buildOrigin == .initial {
22 | action()
23 | }
24 | return context.currentBuilder.buildTree(for: view, in: context)
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Layout/Divider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Divider.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 24.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct Divider : View, Equatable {
10 |
11 | public typealias Body = Never
12 |
13 | public init() {}
14 | }
15 |
16 | extension HTMLTreeBuilder {
17 |
18 | func buildTree(for view: Divider, in context: TreeStateContext)
19 | -> HTMLTreeNode
20 | {
21 | // TBD: Direction?
22 | return HTMLOutputNode(content: "",
23 | escape: false)
24 | }
25 | }
26 |
27 | extension Divider: TreeBuildingView {
28 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
29 | return context.currentBuilder.buildTree(for: self, in: context)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/VirtualDOM/Generic/NotImplementedViewNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotImplementedView.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 08.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | class NotImplementedViewNode : HTMLLeafNode {
10 |
11 | var elementID: ElementID { return ElementID.noElementID }
12 |
13 | func generateHTML(into html: inout String) {
14 | assertionFailure("NOT IMPLEMENTED")
15 | }
16 | func generateChanges(from oldNode : HTMLTreeNode,
17 | into changeset : inout [ HTMLChange ],
18 | in context : TreeStateContext)
19 | {
20 | assertionFailure("NOT IMPLEMENTED")
21 | }
22 |
23 | func dump(nesting: Int) {
24 | let indent = String(repeating: " ", count: nesting)
25 | print("\(indent)NOT IMPLEMENTED!");
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/TabbedView/TabItemLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabItemLabel.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 18.06.19.
6 | // Copyright © 2019-2020 Helge Heß. All rights reserved.
7 | //
8 |
9 | public extension View {
10 |
11 | func tabItem(_ item: V) -> some View {
12 | // The official doesn't carry a type here, but just `AnyView?`. Hm.
13 | return modifier(TraitWritingModifier(content:
14 | TabItemLabel(value: AnyView(item))))
15 | }
16 |
17 | @available(*, deprecated, renamed: "tabItem")
18 | func tabItemLabel(_ item: V) -> some View {
19 | return modifier(TraitWritingModifier(content:
20 | TabItemLabel(value: AnyView(item))))
21 | }
22 | }
23 |
24 | public struct TabItemLabel: Trait {
25 | public typealias Value = AnyView
26 | let value : AnyView
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/VerticalAlignment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VerticalAlignment.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 05.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public enum VerticalAlignment : Equatable {
10 |
11 | case top, center, bottom, firstTextBaseline, lastTextBaseline
12 |
13 | }
14 |
15 | extension VerticalAlignment {
16 |
17 | var flexBoxValue : String {
18 |
19 | switch self {
20 | case .top: return "flex-start"
21 | case .center: return "center"
22 | case .bottom: return "flex-end"
23 | case .firstTextBaseline, .lastTextBaseline:
24 | print("WARN: unsupported alignment property:", self)
25 | return "flex-start"
26 | }
27 | }
28 | }
29 | extension VerticalAlignment: CSSStyleValue {
30 | public var cssStringValue: String { return flexBoxValue }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/HTML/HTML.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTML.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 11.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct HTML : View, Equatable {
10 |
11 | public typealias Body = Never
12 |
13 | let content : String
14 | let escape : Bool
15 |
16 | public init(_ content: String, escape: Bool = false) {
17 | self.content = content
18 | self.escape = escape
19 | }
20 |
21 | }
22 |
23 | extension HTMLTreeBuilder {
24 |
25 | func buildTree(for view: HTML, in context: TreeStateContext) -> HTMLTreeNode
26 | {
27 | return HTMLOutputNode(content: view.content, escape: view.escape)
28 | }
29 | }
30 |
31 | extension HTML: TreeBuildingView {
32 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
33 | return context.currentBuilder.buildTree(for: self, in: context)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/HorizontalAlignment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HorizontalAlignment.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 05.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public enum HorizontalAlignment : Equatable {
10 |
11 | case leading, center, trailing
12 |
13 | /// Hack: Tell a flexbox to stretch its contents.
14 | ///
15 | /// This should be automatic depending on the contents of the vstack.
16 | case stretch
17 | }
18 |
19 | extension HorizontalAlignment {
20 |
21 | var flexBoxValue : String {
22 | switch self {
23 | case .leading : return "flex-start"
24 | case .center : return "center"
25 | case .trailing : return "flex-end"
26 | case .stretch : return "stretch"
27 | }
28 | }
29 | }
30 |
31 | extension HorizontalAlignment: CSSStyleValue {
32 | public var cssStringValue: String { return flexBoxValue }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Layout/Spacer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Spacer.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 08.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct Spacer : View, Equatable {
10 |
11 | public typealias Body = Never
12 |
13 | public let minLength: Length?
14 |
15 | public init(minLength: Length? = nil) {
16 | self.minLength = minLength
17 | }
18 | }
19 |
20 | extension HTMLTreeBuilder {
21 | func buildTree(for view: Spacer, in context: TreeStateContext)
22 | -> HTMLTreeNode
23 | {
24 | return FlexBoxSpacerNode(elementID: context.currentElementID,
25 | minLength: view.minLength)
26 | }
27 | }
28 |
29 | extension Spacer: TreeBuildingView {
30 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
31 | return context.currentBuilder.buildTree(for: self, in: context)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Shapes/Rectangle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Rectangle.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 24.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | @available(*, unavailable)
10 | public struct Rectangle: InsettableShape {
11 |
12 | public var body: some View {
13 | return ShapeView(shape: self, style: ForegroundStyle())
14 | }
15 |
16 | public func path(in rect: UXRect) -> Path {
17 | return Path(rect)
18 | }
19 | public func inset(by amount: Length) -> some Shape {
20 | return Inset(amount: amount)
21 | }
22 |
23 | public struct Inset: Shape {
24 |
25 | public var amount : Length
26 |
27 | public var body: some View {
28 | return ShapeView(shape: self, style: ForegroundStyle())
29 | }
30 |
31 | public func path(in rect: UXRect) -> Path {
32 | return Path(rect.insetBy(amount))
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/SemanticUI/SUISegment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SUISegment.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 17.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct SUISegment: View {
10 | // TODO: clean this up
11 |
12 | let styles : CSSStyles?
13 | let content : Content
14 |
15 | public init(width : Length? = .percent(100),
16 | height : Length? = nil,
17 | @ViewBuilder content: () -> Content)
18 | {
19 | // TODO: support relativeHeight
20 | var styles = CSSStyles()
21 | if let v = width { styles[.width] = v }
22 | if let v = height { styles[.height] = v }
23 | self.styles = styles.isEmpty ? nil : styles
24 | self.content = content()
25 | }
26 |
27 | public var body: some View {
28 | HTMLContainer(classes: ["ui", "segment"], styles: styles) {
29 | content
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/ShadowModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 |
3 | public extension View {
4 | func shadow(color: Color = .black, radius: Length, x: Length = 0, y: Length = 0) -> ModifiedContent
5 | {
6 | return modifier(ShadowModifier(value: Shadow(color: color, radius: radius, x: x, y: y)))
7 | }
8 | }
9 |
10 | public struct ShadowModifier: ViewModifier {
11 | public typealias Value = Shadow
12 |
13 | let value : Value
14 |
15 | public func buildTree(for view: T, in context: TreeStateContext)
16 | -> HTMLTreeNode
17 | {
18 | context.appendContentElementIDComponent()
19 | let child = context.currentBuilder.buildTree(for: view, in: context)
20 | context.deleteLastElementIDComponent()
21 |
22 | return HTMLShadowNode(elementID: context.currentElementID,
23 | value: value, content: child)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Properties/ObservableObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObservableObject.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 06.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | // FIXME: Combine requires 10.15, maybe provide a simple alternative
10 | #if canImport(Combine)
11 | import Combine
12 | #elseif canImport(OpenCombine)
13 | import OpenCombine
14 | #endif
15 |
16 | public protocol ObservableObject: AnyObject, DynamicViewProperty, Identifiable {
17 |
18 | associatedtype PublisherType : Publisher
19 | where Self.PublisherType.Failure == Never
20 |
21 | var didChange: Self.PublisherType { get }
22 |
23 | }
24 |
25 | public extension ObservableObject {
26 |
27 | subscript(keyPath: ReferenceWritableKeyPath) -> Binding {
28 | return Binding(getValue: { return self[keyPath: keyPath] },
29 | setValue: { self[keyPath: keyPath] = $0 })
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/ViewModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModifier.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 10.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public protocol ViewModifier {
10 | // We implement this, but it may work quite different to the original.
11 |
12 | // associatedtype Body : View
13 | //func body(content: Self.Content) -> Self.Body
14 |
15 | func push(to context: TreeStateContext)
16 | func pop (from context: TreeStateContext)
17 |
18 | func buildTree(for view: T, in context: TreeStateContext)
19 | -> HTMLTreeNode
20 | }
21 |
22 | extension ViewModifier {
23 |
24 | public func push(to context: TreeStateContext) {}
25 | public func pop (from context: TreeStateContext) {}
26 |
27 | public func buildTree(for view: T, in context: TreeStateContext)
28 | -> HTMLTreeNode
29 | {
30 | return context.currentBuilder.buildTree(for: view, in: context)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/EnvironmentObjectWritingModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EnvironmentObjectWritingModifier.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 21.06.19.
6 | // Copyright © 2019-2020 Helge Heß. All rights reserved.
7 | //
8 |
9 | public extension View {
10 |
11 | func environmentObject(_ object: O) -> some View {
12 | return modifier(EnvironmentObjectWritingModifier(object))
13 | }
14 | }
15 |
16 | public struct EnvironmentObjectWritingModifier
17 | : ViewModifier
18 | {
19 | private let object : O
20 |
21 | init(_ object: O) {
22 | self.object = object
23 | }
24 |
25 | public func push(to context: TreeStateContext) {
26 | var newEnvironment = context.environment
27 | newEnvironment[EnvironmentObjectKey.self] = object
28 | context.environmentStack.append(newEnvironment)
29 | }
30 | public func pop(from context: TreeStateContext) {
31 | context.environmentStack.removeLast()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/VirtualDOM/Generic/EmptyNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmptyNode.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 10.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | struct EmptyNode: HTMLLeafNode {
10 | // Note: This still needs an ID, if used in an if-else ViewBuilder, the
11 | // changes will try to replace the ID with the other contents.
12 |
13 | let elementID: ElementID
14 |
15 | func generateHTML(into html: inout String) {
16 | html += ""
19 | }
20 | func generateChanges(from oldNode : HTMLTreeNode,
21 | into changeset : inout [ HTMLChange ],
22 | in context : TreeStateContext)
23 | {
24 | guard let _ = sameType(oldNode, &changeset) else { return }
25 | // both empty
26 | }
27 |
28 | func dump(nesting: Int) {
29 | let indent = String(repeating: " ", count: nesting)
30 | print("\(indent)")
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Generic/ForEach.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ForEach.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 11.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct ForEach : DynamicViewContent
10 | where Data: RandomAccessCollection, Data.Element: Identifiable
11 | {
12 | // Aka WORepetition
13 |
14 | public typealias Body = Never
15 |
16 | public let data : Data
17 |
18 | let content : ( Data.Element ) -> Content
19 |
20 | public init(_ data: Data,
21 | content: @escaping ( Data.Element.IdentifiedValue ) -> Content)
22 | {
23 | self.init(data, content: { value in content(value.identifiedValue) })
24 | }
25 | init(_ data: Data, content: @escaping ( Data.Element ) -> Content) {
26 | self.data = data
27 | self.content = content
28 | }
29 | }
30 |
31 | extension ForEach: TreeBuildingView {
32 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
33 | return context.currentBuilder.buildTree(for: self, in: context)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/VirtualDOM/HTMLLeafNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 20.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public protocol HTMLLeafNode: HTMLTreeNode {
10 | }
11 |
12 | extension HTMLLeafNode {
13 |
14 | func rewrite(using rewriter: ( HTMLTreeNode ) -> RewriteAction)
15 | -> HTMLTreeNode
16 | {
17 | switch rewriter(self) {
18 | case .replaceAndReturn(let newNode): return newNode
19 | case .recurse: return self
20 | }
21 | }
22 |
23 | func takeValue(_ webID: [ String ], value: String,
24 | in context: TreeStateContext) throws
25 | {
26 | throw WebInvocationError.inactiveElement(webID)
27 | }
28 | func invoke(_ webID: [ String ], in context: TreeStateContext) throws {
29 | throw WebInvocationError.inactiveElement(webID)
30 | }
31 |
32 | var children: [ HTMLTreeNode ] { return [] }
33 | func dump(nesting: Int) {
34 | let indent = String(repeating: " ", count: nesting)
35 | print("\(indent)<\(self)/>")
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/VirtualDOM/Images/CGImageNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGImage.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 22.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | #if canImport(CoreGraphics)
10 |
11 | import struct Foundation.Data
12 | import class CoreGraphics.CGImage
13 | import ImageIO
14 |
15 | fileprivate let defaultImageDataCapacity = 32000 // TBD
16 |
17 | extension CGImage {
18 |
19 | // TBD: should that have scale data already?
20 | func generateData(type: String) -> Data? {
21 | guard let data = CFDataCreateMutable(nil, defaultImageDataCapacity) else {
22 | return nil
23 | }
24 | guard let destination = CGImageDestinationCreateWithData(
25 | data, type as CFString, 1, nil
26 | ) else { return nil }
27 |
28 | CGImageDestinationAddImage(destination, self, nil)
29 | guard CGImageDestinationFinalize(destination) else {
30 | print("could not render CGImage ...")
31 | return nil
32 | }
33 | return data as Data
34 | }
35 |
36 | }
37 |
38 | #endif // canImport(CoreGraphics)
39 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Generic/ConditionalContent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConditionalContent.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 10.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct ConditionalContent : View
10 | where TrueContent : View, FalseContent : View
11 | {
12 | // When building, we only ever get one side, either True or False.
13 | // That means if the condition toggles, the full child tree won't
14 | // match up anymore? (unless they have an indentical structure?)
15 | public typealias Body = Never
16 |
17 | enum Content {
18 | case first(TrueContent)
19 | case second(FalseContent)
20 | }
21 | let content : Content
22 |
23 | init(first : TrueContent) { content = .first(first) }
24 | init(second : FalseContent) { content = .second(second) }
25 |
26 | }
27 |
28 | extension ConditionalContent: TreeBuildingView {
29 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
30 | return context.currentBuilder.buildTree(for: self, in: context)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Layout/HStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HStack.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 08.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct HStack : View, CustomStringConvertible {
10 | public typealias Body = Never
11 |
12 | let alignment : VerticalAlignment
13 | let spacing : Length?
14 | let content : Content
15 |
16 | public var description: String {
17 | if let spacing = spacing {
18 | return ""
19 | }
20 | else {
21 | return ""
22 | }
23 | }
24 |
25 | public init(alignment: VerticalAlignment = .center,
26 | spacing: Length? = nil, @ViewBuilder content: () -> Content)
27 | {
28 | self.alignment = alignment
29 | self.spacing = spacing
30 | self.content = content()
31 | }
32 | }
33 |
34 | extension HStack: TreeBuildingView {
35 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
36 | return context.currentBuilder.buildTree(for: self, in: context)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Layout/VStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VStack.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 05.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct VStack : View, CustomStringConvertible {
10 | public typealias Body = Never
11 |
12 | let alignment : HorizontalAlignment
13 | let spacing : Length?
14 | let content : Content
15 |
16 | public init(alignment: HorizontalAlignment = .center,
17 | spacing: Length? = nil, @ViewBuilder content: () -> Content)
18 | {
19 | self.alignment = alignment
20 | self.spacing = spacing
21 | self.content = content()
22 | }
23 |
24 | public var description: String {
25 | if let spacing = spacing {
26 | return ""
27 | }
28 | else {
29 | return ""
30 | }
31 | }
32 | }
33 |
34 | extension VStack: TreeBuildingView {
35 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
36 | return context.currentBuilder.buildTree(for: self, in: context)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Shapes/RoundedRectangle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoundedRectangle.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 24.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | @available(*, unavailable)
10 | public struct RoundedRectangle: InsettableShape {
11 |
12 | public var cornerRadius: Length
13 |
14 | public var body: some View {
15 | return ShapeView(shape: self, style: ForegroundStyle())
16 | }
17 |
18 | public func path(in rect: UXRect) -> Path {
19 | return Path(roundedRect: rect, cornerRadius: cornerRadius)
20 | }
21 | public func inset(by amount: Length) -> some Shape {
22 | return Inset(cornerRadius: cornerRadius, amount: amount)
23 | }
24 |
25 | public struct Inset: Shape {
26 |
27 | public var cornerRadius : Length
28 | public var amount : Length
29 |
30 | public var body: some View {
31 | return ShapeView(shape: self, style: ForegroundStyle())
32 | }
33 |
34 | public func path(in rect: UXRect) -> Path {
35 | return Path(roundedRect: rect.insetBy(amount), cornerRadius: cornerRadius)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Shapes/SizedShape.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SizedShape.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 24.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public extension Shape {
10 |
11 | func size(_ size: UXSize) -> SizedShape {
12 | return SizedShape(shape: self, size: size)
13 | }
14 |
15 | func size(width: Length, height: Length) -> SizedShape {
16 | // Hmmmm, this takes a Length. So UXSize should probably be based around
17 | // lengths? CSS would probably deal with that quite right.
18 | switch ( width, height ) {
19 | case ( .pixels(let w), .pixels(let h) ):
20 | return SizedShape(shape: self, size: UXSize(width: w, height: h))
21 | default:
22 | fatalError("unsupported Length unit")
23 | }
24 | }
25 | }
26 |
27 | public struct SizedShape: Shape, Equatable {
28 |
29 | public var shape : S
30 | public var size : UXSize
31 |
32 | public func path(in rect: UXRect) -> Path {
33 | // No idea whether this is correct.
34 | return shape.path(in: UXRect(origin: rect.origin, size: size))
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Navigation/NavigationContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationContext.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 22.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | #if canImport(Combine)
10 | import Combine
11 | #elseif canImport(OpenCombine)
12 | import OpenCombine
13 | #endif
14 |
15 | final class NavigationContext: ObservableObject {
16 |
17 | private(set) var activeTargetView : AnyView {
18 | didSet { didChange.send(()) }
19 | }
20 |
21 | init(_ initialTargetView: AnyView) {
22 | self.activeTargetView = initialTargetView
23 | }
24 | init(_ initialTargetView: V) {
25 | self.activeTargetView = AnyView(initialTargetView)
26 | }
27 |
28 | func navigate(to view: V) {
29 | activeTargetView = AnyView(view)
30 | }
31 | func navigate(to view: AnyView) {
32 | activeTargetView = view
33 | }
34 |
35 | var didChange = PassthroughSubject()
36 | }
37 |
38 | extension NavigationContext: CustomStringConvertible {
39 | var description: String {
40 | return ""
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/VirtualDOM/Layout/HTMLScrollNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTMLScrollView.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 23.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | struct HTMLScrollNode : HTMLWrappingNode {
10 |
11 | let elementID : ElementID
12 | let content : HTMLTreeNode // type erased, hm
13 |
14 | func nodeByApplyingNewContent(_ newContent: HTMLTreeNode) -> Self {
15 | return HTMLScrollNode(elementID: elementID, content: newContent)
16 | }
17 |
18 | var containsSpacer: Bool {
19 | // I guess, we behave like a spacer and grab all available width?
20 | return false
21 | }
22 |
23 | func generateHTML(into html: inout String) {
24 | html += ""
27 | defer { html += "
" }
28 |
29 | content.generateHTML(into: &html)
30 | }
31 |
32 | // MARK: - Debug
33 |
34 | public func dump(nesting: Int) {
35 | let indent = String(repeating: " ", count: nesting)
36 | print("\(indent)")
37 | content.dump(nesting: nesting + 1)
38 | print("\(indent)")
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Generic/AnyView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnyView.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 17.06.19.
6 | // Copyright © 2019-2020 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct AnyView : View {
10 |
11 | public typealias Body = Never
12 |
13 | #if DEBUG
14 | private let viewDescription : () -> String
15 | #endif
16 | let viewType : Any.Type
17 | var bodyBuild : ( TreeStateContext ) -> HTMLTreeNode
18 |
19 | public init(_ view: V) {
20 | #if DEBUG
21 | self.viewDescription = { return String(describing: view) }
22 | #endif
23 | self.viewType = V.self
24 | self.bodyBuild = { context in
25 | context.currentBuilder.buildTree(for: view, in: context)
26 | }
27 | }
28 | }
29 |
30 | extension AnyView: CustomStringConvertible {
31 |
32 | public var description: String {
33 | #if DEBUG
34 | return ""
35 | #else
36 | return ""
37 | #endif
38 | }
39 | }
40 |
41 | extension AnyView: TreeBuildingView {
42 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
43 | return context.currentBuilder.buildTree(for: self, in: context)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/ViewHosting/HTMLStringHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WOHelpers.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 08.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | extension String {
10 |
11 | mutating func appendContentString(_ s: String) {
12 | append(s)
13 | }
14 | mutating func appendContentHTMLString(_ s: String) {
15 | append(s.htmlEscaped)
16 | }
17 | mutating func appendContentHTMLAttributeValue(_ s: String) {
18 | append(s.htmlEscaped)
19 | }
20 |
21 | mutating func appendAttribute(_ name: String, _ value: String?) {
22 | append(" ")
23 | appendContentString(name) // TODO: escape properly!
24 | if let value = value {
25 | append("=\"")
26 | appendContentHTMLAttributeValue(value)
27 | append("\"")
28 | }
29 | }
30 | }
31 |
32 | fileprivate let escapeMap : [ Character : String ] = [
33 | "<" : "<", ">": ">", "&": "&", "\"": """
34 | ]
35 | extension Character {
36 | var htmlEscaped : String {
37 | return escapeMap[self] ?? "\(self)"
38 | }
39 | }
40 | extension StringProtocol {
41 | var htmlEscaped : String {
42 | return map { escapeMap[$0] ?? String($0) }.reduce("", +)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Environment/Environment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Environment.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 10.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | @propertyWrapper
10 | public struct Environment: DynamicViewProperty {
11 |
12 | // (\EnvironmentValues.isEnabled) or just (\.isEnabled)
13 | let keyPath : KeyPath
14 |
15 | public init(_ keyPath: KeyPath) {
16 | self.keyPath = keyPath
17 | }
18 |
19 | // DynamicViewProperty
20 | // update => this updates the value from the environment I think
21 | // how do we receive the environment? Is that a global?
22 |
23 | private var _value: Value?
24 |
25 | public var wrappedValue: Value {
26 | guard let value = _value else {
27 | fatalError("you cannot access @Environment outside of `body`")
28 | }
29 | return value
30 | }
31 |
32 | public mutating func update() {
33 | guard let context = DynamicViewPropertyHelpers.currentContext else {
34 | assertionFailure("you cannot access @Environment outside of `body`")
35 | return
36 | }
37 |
38 | _value = context.environment[keyPath: keyPath]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Navigation/NavigationLink.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationLink.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 22.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct NavigationLink: View {
10 | // TBD: What is NavigationDestinationLink? Same like a navcontext?
11 |
12 | @EnvironmentObject private var navigationContext: NavigationContext
13 |
14 | private let destination : Destination
15 | private let onTrigger : (() -> Bool)?
16 | private let content : Content
17 |
18 | public init(destination : Destination,
19 | onTrigger : (() -> Bool)? = nil,
20 | @ViewBuilder content: () -> Content)
21 | {
22 | self.destination = destination
23 | self.onTrigger = onTrigger
24 | self.content = content()
25 | }
26 |
27 | public var body: some View {
28 | Button(action: onClick, label: content)
29 | .relativeWidth(1.0)
30 | }
31 |
32 | func onClick() {
33 | if let onTrigger = onTrigger, !onTrigger() { return }
34 | navigationContext.navigate(to: destination)
35 | }
36 | }
37 |
38 | @available(*, deprecated, renamed: "NavigationLink")
39 | public typealias NavigationButton = NavigationLink
40 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/EnvironmentKeyWritingModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EnvironmentKeyWritingModifier.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 10.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public extension View {
10 |
11 | func environment(_ keyPath: WritableKeyPath,
12 | _ value: V)
13 | -> ModifiedContent>
14 | {
15 | return modifier(EnvironmentKeyWritingModifier(keyPath, value))
16 | }
17 |
18 | }
19 |
20 | public struct EnvironmentKeyWritingModifier: ViewModifier {
21 |
22 | typealias EnvironmentKeyPath = WritableKeyPath
23 |
24 | private let keyPath : EnvironmentKeyPath
25 | private let value : Value
26 |
27 | init(_ keyPath: EnvironmentKeyPath, _ value: Value) {
28 | self.keyPath = keyPath
29 | self.value = value
30 | }
31 |
32 | public func push(to context: TreeStateContext) {
33 | var newEnvironment = context.environment
34 | newEnvironment[keyPath: keyPath] = value
35 | context.environmentStack.append(newEnvironment)
36 | }
37 | public func pop(from context: TreeStateContext) {
38 | context.environmentStack.removeLast()
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/FrameModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FrameModifier.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 17.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public extension View {
10 |
11 | func frame(width: Length? = nil, height: Length? = nil,
12 | alignment: Alignment = .center)
13 | -> ModifiedContent
14 | {
15 | return modifier(FrameLayout(value:
16 | .init(width: width, height: height, alignment: alignment)))
17 | }
18 | }
19 |
20 | public struct FrameLayout: ViewModifier {
21 |
22 | public typealias Content = Value
23 |
24 | public struct Value: Equatable {
25 | let width : Length?
26 | let height : Length?
27 | let alignment : Alignment
28 | }
29 |
30 | let value : Value
31 |
32 | public func buildTree(for view: T, in context: TreeStateContext)
33 | -> HTMLTreeNode
34 | {
35 | context.appendContentElementIDComponent()
36 | let child = context.currentBuilder.buildTree(for: view, in: context)
37 | context.deleteLastElementIDComponent()
38 |
39 | return HTMLFrameNode(elementID: context.currentElementID,
40 | value: value, content: child)
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Unsplash/Unsplash.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Unsplash.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 25.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | #if canImport(Foundation)
10 | extension Image {
11 |
12 | public init(_ source: UnsplashSource, label: Text? = nil) {
13 | self.storage = .url(source.url, label: label)
14 | }
15 |
16 | public static func unsplash(scope : UnsplashSource.Scope = .none,
17 | time : UnsplashSource.Time = .all,
18 | size : UXSize = UXSize(width: 640, height: 480),
19 | terms : [ String ]) -> Image
20 | {
21 | return Image(UnsplashSource(
22 | scope: scope, time: time,
23 | size:
24 | UnsplashSource.Size(width: Int(size.width), height: Int(size.height)),
25 | terms: terms)
26 | )
27 | }
28 | public static func unsplash(scope : UnsplashSource.Scope = .none,
29 | time : UnsplashSource.Time = .all,
30 | size : UXSize = UXSize(width: 640, height: 480),
31 | _ terms : String...) -> Image
32 | {
33 | return unsplash(scope: scope, time: time, size: size, terms: terms)
34 | }
35 | }
36 | #endif
37 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Shapes/Path.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Path.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 24.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public enum Path : Equatable {
10 | // TODO: Presumably we could support any kind of Path via SVG, and potentially
11 | // CSS. Which can also mask & clip using pathes.
12 |
13 | case rect (_ bounds: UXRect)
14 | case roundedRect(_ bounds: UXRect, cornerRadius: Length)
15 |
16 | public init(_ rect: UXRect) {
17 | self = .rect(rect)
18 | }
19 | public init(roundedRect bounds: UXRect, cornerRadius: Length) {
20 | self = .roundedRect(bounds, cornerRadius: cornerRadius)
21 | }
22 |
23 | public var isEmpty: Bool { return false }
24 |
25 | public var boundingRect: UXRect {
26 | switch self {
27 | case .rect (let bounds): return bounds
28 | case .roundedRect(let bounds, _): return bounds
29 | }
30 | }
31 | }
32 |
33 | extension Path: Shape {
34 |
35 | public func path(in rect: UXRect) -> Path {
36 | // TBD: Should that scale our rect values?? Or center? Or what? :-)
37 | switch self {
38 | case .rect:
39 | return Path(rect)
40 | case .roundedRect(_, let cornerRadius):
41 | return Path(roundedRect: rect, cornerRadius: cornerRadius)
42 | }
43 | }
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Misc/DebugSwitches.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DebugSwitches.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 25.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | #if DEBUG
10 | let debugComponentScopes = false
11 | let debugInvalidation = false
12 | let debugEnvironmentChanges = false
13 | let debugRequestPhases = false
14 | let debugDumpTrees = false
15 | let debugTraits = false
16 | #else
17 | let debugComponentScopes = false
18 | let debugInvalidation = false
19 | let debugEnvironmentChanges = false
20 | let debugRequestPhases = false
21 | let debugDumpTrees = false
22 | let debugTraits = false
23 | #endif
24 |
25 |
26 |
27 | extension ObservableObject { // can't extend AnyObject ...
28 |
29 | var pointerDescription: String {
30 | return ObjectIdentifier(self).shortRawPointerString
31 | }
32 |
33 | }
34 |
35 | extension ObjectIdentifier {
36 |
37 | var rawPointerString: String {
38 | let s = String(describing: self)
39 | return s.hasPrefix("ObjectIdentifier(")
40 | ? String(s.dropFirst(17).dropLast(1))
41 | : s
42 | }
43 | var shortRawPointerString: String {
44 | #if canImport(Foundation)
45 | rawPointerString.replacingOccurrences(of: "0x0000000", with: "0x")
46 | #else
47 | return rawPointerString
48 | #endif
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile
2 |
3 | # local config
4 | SWIFT_BUILD=swift build
5 | SWIFT_CLEAN=swift package clean
6 | SWIFT_BUILD_DIR=.build
7 |
8 | # docker config
9 | SWIFT_BUILD_IMAGE="helje5/swift-dev:5.2.0"
10 | CONFIGURATION=release
11 | DOCKER_BUILD_DIR=".docker.build"
12 | SWIFT_DOCKER_BUILD_DIR="$(DOCKER_BUILD_DIR)/x86_64-unknown-linux/$(CONFIGURATION)"
13 | DOCKER_BUILD_PRODUCT="$(DOCKER_BUILD_DIR)/$(TOOL_NAME)"
14 |
15 |
16 | SWIFT_SOURCES=\
17 | Sources/*/*/*.swift \
18 | Sources/*/*/*/*.swift
19 |
20 | all:
21 | $(SWIFT_BUILD)
22 |
23 | clean :
24 | $(SWIFT_CLEAN)
25 | # We have a different definition of "clean", might be just German
26 | # pickyness.
27 | rm -rf $(SWIFT_BUILD_DIR)
28 |
29 | $(DOCKER_BUILD_PRODUCT): $(SWIFT_SOURCES)
30 | time docker run --rm \
31 | -v "$(PWD):/src" \
32 | -v "$(PWD)/$(DOCKER_BUILD_DIR):/src/.build" \
33 | "$(SWIFT_BUILD_IMAGE)" \
34 | bash -c 'cd /src && swift build -c $(CONFIGURATION)'
35 | ls -lah $(DOCKER_BUILD_PRODUCT)
36 |
37 | docker-all: $(DOCKER_BUILD_PRODUCT)
38 |
39 | docker-clean:
40 | rm -rf $(DOCKER_BUILD_PRODUCT)
41 |
42 | docker-distclean:
43 | rm -rf $(DOCKER_BUILD_DIR)
44 |
45 | distclean: clean docker-distclean
46 |
47 | docker-emacs:
48 | docker run --rm -it \
49 | -v "$(PWD):/src" \
50 | -v "$(PWD)/$(DOCKER_BUILD_DIR):/src/.build" \
51 | "$(SWIFT_BUILD_IMAGE)" \
52 | emacs /src
53 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/BackgroundModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BackgroundModifier.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 10.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public extension View {
10 |
11 | func background(_ color: Color? = nil , cornerRadius: Length? = nil)
12 | -> ModifiedContent
13 | {
14 | return modifier(BackgroundModifier(value: ( color, cornerRadius )))
15 | }
16 | }
17 |
18 | public struct BackgroundModifier: ViewModifier {
19 | // This is very different, we don't do the shape thing yet.
20 | // ShapeStyle => Shape into View
21 | // Shape => no accessible inits
22 | // Presumably Shape would be an SVG thing? But they also use it for
23 | // clipping etc. So maybe rather CSS
24 | // So:
25 | // We just have color and radius :-)
26 |
27 | public typealias Value = ( color: Color?, cornerRadius: Length? )
28 |
29 | let value : Value
30 |
31 |
32 | public func buildTree(for view: T, in context: TreeStateContext)
33 | -> HTMLTreeNode
34 | {
35 | context.appendContentElementIDComponent()
36 | let child = context.currentBuilder.buildTree(for: view, in: context)
37 | context.deleteLastElementIDComponent()
38 |
39 | return HTMLBackgroundNode(elementID: context.currentElementID,
40 | value: value, content: child)
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/VirtualDOM/HTML/HTMLOutputNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTMLOutputNode.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 23.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | struct HTMLOutputNode: HTMLLeafNode, Equatable {
10 |
11 | var elementID : ElementID { return ElementID.noElementID }
12 | let content : String
13 | let escape : Bool
14 |
15 | init(content: String, escape: Bool) {
16 | self.content = content
17 | self.escape = escape
18 | }
19 |
20 | func generateHTML(into html: inout String) {
21 | if escape {
22 | html.appendContentHTMLString(content)
23 | }
24 | else {
25 | html.appendContentString(content)
26 | }
27 | }
28 |
29 | func generateChanges(from oldNode : HTMLTreeNode,
30 | into changeset : inout [ HTMLChange ],
31 | in context : TreeStateContext)
32 | {
33 | guard let oldNode = sameType(oldNode, &changeset) else { return }
34 |
35 | if oldNode.content != content || oldNode.escape != escape {
36 | print("WARN: can't change raw HTML views:", self)
37 | }
38 | }
39 |
40 | // MARK: - Debugging
41 |
42 | public func dump(nesting: Int) {
43 | let indent = String(repeating: " ", count: nesting)
44 | if escape { print("\(indent)") }
45 | else { print("\(indent)") }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Forms/SUIButtonStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SUIButtonStyle.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 22.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct SUIButtonStyle: ButtonStyle {
10 |
11 | public typealias Label = ButtonStyleLabel
12 | public typealias Member = StaticMember
13 |
14 | public func body(configuration: Button) -> Body {
15 | return Body(configuration: configuration)
16 | }
17 |
18 | public struct Body: View {
19 | public typealias Body = Never
20 |
21 | let configuration : Button
22 |
23 | }
24 | }
25 |
26 | extension HTMLTreeBuilder {
27 |
28 | func buildTree(for view: SUIButtonStyle.Body, in context: TreeStateContext)
29 | -> HTMLTreeNode
30 | {
31 | let childTree = _buildContent(view.configuration.label, in: context)
32 | return SUIButtonNode(elementID : context.currentElementID,
33 | isEnabled : context.environment.isEnabled,
34 | isActive : false,
35 | action : view.configuration.action,
36 | content : childTree)
37 | }
38 |
39 | }
40 |
41 | extension SUIButtonStyle.Body: TreeBuildingView {
42 |
43 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
44 | return context.currentBuilder.buildTree(for: self, in: context)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Modifiers/PaddingModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Padding.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 10.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public extension View {
10 |
11 | func padding(_ insets: EdgeInsets) -> ModifiedContent {
12 | return modifier(PaddingLayout(value: .insets(insets)))
13 | }
14 | func padding(_ edges: Edge.Set = .all, _ length: Length? = nil)
15 | -> ModifiedContent
16 | {
17 | return modifier(PaddingLayout(value: .edges(edges, length)))
18 | }
19 | func padding(_ length: Length) -> ModifiedContent {
20 | return modifier(PaddingLayout(value: .insets(EdgeInsets(length))))
21 | }
22 | }
23 |
24 | public struct PaddingLayout: ViewModifier {
25 |
26 | public typealias Content = Value
27 |
28 | public enum Value: Equatable {
29 | case insets(EdgeInsets)
30 | case edges(Edge.Set, Length?)
31 | }
32 |
33 | let value : Value
34 |
35 | public func buildTree(for view: T, in context: TreeStateContext)
36 | -> HTMLTreeNode
37 | {
38 | let child = context.currentBuilder._buildInLayoutContext(view, in: context)
39 | return HTMLPaddingNode(elementID: context.currentElementID,
40 | value: value,
41 | localLayoutInfo: context.localLayoutInfo,
42 | content: child)
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/TabbedView/SUITabView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SUITabView.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 18.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public typealias TabView = SUITabView
10 |
11 | public struct SUITabView: View {
12 | // TODO: this could have two modes, one which re-renders on switch and one
13 | // which always pregenerates all tab content.
14 | // plus: one which loads on demand ;-)
15 |
16 | let selection : Binding
17 | let content : Content
18 |
19 | public init(selection: Binding,
20 | @ViewBuilder content: () -> Content)
21 | {
22 | self.selection = selection
23 | self.content = content()
24 | }
25 |
26 | public var body: some View { return content }
27 | }
28 |
29 | extension TabView where SelectionValue == Int {
30 |
31 | public init(@ViewBuilder content: () -> Content) {
32 | // The default if no selection binding is provided
33 | var selectionHolder = 0
34 | self.selection = Binding(getValue: { return selectionHolder},
35 | setValue: { selectionHolder = $0 })
36 | self.content = content()
37 | }
38 |
39 | }
40 |
41 | extension SUITabView: TreeBuildingView {
42 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
43 | return context.currentBuilder.buildTree(for: self, in: context)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Generic/TapAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TapAction.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 23.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public extension View {
10 |
11 | func onTapGesture(count: Int = 1, _ action: @escaping () -> Void)
12 | -> TapActionView
13 | {
14 | assert(count < 2, "only supporting single-click")
15 | return TapActionView(count: count, action: action, content: self)
16 | }
17 |
18 | }
19 |
20 | public struct TapActionView: View {
21 | public typealias Body = Never
22 | let count : Int
23 | let action : () -> Void
24 | let content : Content
25 | }
26 |
27 | extension HTMLTreeBuilder {
28 |
29 | func buildTree(for view: TapActionView,
30 | in context: TreeStateContext) -> HTMLTreeNode
31 | {
32 | let childTree = _buildContent(view.content, in: context)
33 | return HTMLClickContainerNode(elementID: context.currentElementID,
34 | isEnabled: context.environment.isEnabled,
35 | isDouble: view.count == 2,
36 | action: view.action, content: childTree)
37 | }
38 |
39 | }
40 |
41 | extension TapActionView: TreeBuildingView {
42 |
43 | func buildTree(in context: TreeStateContext) -> HTMLTreeNode {
44 | return context.currentBuilder.buildTree(for: self, in: context)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Values/EdgeInsets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EdgeInsets.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 09.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public enum Edge: Int8, CaseIterable, Hashable {
10 |
11 | case top, leading, bottom, trailing
12 |
13 | public struct Set: OptionSet, Hashable {
14 | public let rawValue : Int
15 | public init(rawValue: Int) {
16 | self.rawValue = rawValue
17 | }
18 |
19 | public static let bottom = Set(rawValue: 1 << 0)
20 | public static let top = Set(rawValue: 1 << 1)
21 | public static let leading = Set(rawValue: 1 << 2)
22 | public static let trailing = Set(rawValue: 1 << 3)
23 | public static let horizontal = Set(rawValue: 1 << 4)
24 | public static let vertical = Set(rawValue: 1 << 5)
25 |
26 | public static let all = Set(rawValue: 1 << 6)
27 | // TBD: is this all flags, or a special context?
28 | }
29 | }
30 |
31 | public struct EdgeInsets: Hashable {
32 | public var top: Length, leading: Length, bottom: Length, trailing: Length
33 |
34 | public init(_ length: Length) {
35 | top = length; leading = length; bottom = length; trailing = length
36 | }
37 | }
38 |
39 | extension EdgeInsets: CSSStyleValue {
40 |
41 | public var cssStringValue: String { // clockwise in CSS
42 | return "\(top.cssStringValue) \(trailing.cssStringValue) "
43 | + "\(bottom.cssStringValue) \(leading.cssStringValue)"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/WASMHosting/JSONPolyfill.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Carson Katri on 5/28/20.
6 | //
7 |
8 | import JavaScriptKit
9 |
10 | // This is just a wrapper around the JavaScript JSON object that converts the JSObjectRef to a Decodable type.
11 | public class JSONDecoder {
12 | public init() {
13 |
14 | }
15 |
16 | // This should be a minor hurdle if I can leverage the JavaScript JSON type
17 | public func decode(_ type: T.Type, from jsonString: String) throws -> T {
18 | guard let json = JSObjectRef.global.JSON.object?.parse?(jsonString) else {
19 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
20 | }
21 | return try JSValueDecoder().decode(from: json)
22 | }
23 |
24 | public func decode(_ type: T.Type, from data: Data) throws -> T {
25 | guard let json = JSObjectRef.global.JSON.object?.parse?(data.stringValue) else {
26 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
27 | }
28 | do {
29 | return try JSValueDecoder().decode(from: json)
30 | } catch {
31 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Properties/BindingConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BindingConvertible.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 05.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | @dynamicMemberLookup public protocol BindingConvertible {
10 | // Kinda like objects being able to vend a WOAssociation
11 | // `State` is a BindingConvertible.
12 |
13 | associatedtype Value
14 |
15 | var binding : Binding { get }
16 |
17 | subscript(dynamicMember path: WritableKeyPath)
18 | -> Binding { get }
19 | }
20 |
21 | public extension BindingConvertible {
22 |
23 | subscript(dynamicMember path: WritableKeyPath)
24 | -> Binding
25 | {
26 | return Binding(getValue: { return self.binding.wrappedValue[keyPath: path] },
27 | setValue: { self.binding.wrappedValue[keyPath: path] = $0 })
28 | }
29 | }
30 |
31 | public extension BindingConvertible {
32 |
33 | func zip(with rhs: T)
34 | -> Binding< ( Self.Value, T.Value ) >
35 | {
36 | return Binding(
37 | getValue: { return ( self.binding.wrappedValue, rhs.binding.wrappedValue ) },
38 | setValue: { ( newLHS, newRHS ) in
39 | self.binding.wrappedValue = newLHS
40 | rhs.binding.wrappedValue = newRHS
41 | }
42 | )
43 | }
44 |
45 | }
46 |
47 | extension Binding : BindingConvertible {
48 |
49 | public var binding : Self { return self }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/SwiftWebUI/Views/Forms/SUIToggleStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SUIToggleStyle.swift
3 | // SwiftWebUI
4 | //
5 | // Created by Helge Heß on 20.06.19.
6 | // Copyright © 2019 Helge Heß. All rights reserved.
7 | //
8 |
9 | public struct SUIToggleStyle: ToggleStyle {
10 |
11 | public typealias Label = ToggleStyleLabel
12 | public typealias Member = StaticMember
13 |
14 | public func body(configuration: Toggle