Skip to content

Commit

Permalink
Merge pull request #47 from joshaber/parent
Browse files Browse the repository at this point in the history
Support view-less Elements
joshaber committed Apr 17, 2015
2 parents e8b0f45 + 4da9de5 commit ce59bfd
Showing 18 changed files with 190 additions and 155 deletions.
2 changes: 1 addition & 1 deletion External/SwiftBox
4 changes: 2 additions & 2 deletions Few-Mac/Mac.swift
Original file line number Diff line number Diff line change
@@ -21,6 +21,6 @@ internal func markNeedsDisplay(view: ViewType) {
view.needsDisplay = true
}

internal func configureViewToAutoresize(view: ViewType) {
view.autoresizingMask = .ViewWidthSizable | .ViewHeightSizable
internal func configureViewToAutoresize(view: ViewType?) {
view?.autoresizingMask = .ViewWidthSizable | .ViewHeightSizable
}
2 changes: 1 addition & 1 deletion Few-Mac/QuickLook.swift
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import Foundation

extension Element {
public func debugQuickLookObject() -> AnyObject? {
let realizedSelf = realize()
let realizedSelf = realize(nil)
return realizedSelf.view
}

16 changes: 11 additions & 5 deletions Few-Mac/ScrollView.swift
Original file line number Diff line number Diff line change
@@ -48,6 +48,13 @@ private class FewScrollView: NSView {
}
}

private class RealizedScrollViewElement: RealizedElement {
private override func addRealizedViewForChild(child: RealizedElement) {
let scrollVew = view as! FewScrollView
scrollVew.scrollView.documentView = child.view
}
}

private class ScrollViewElement: Element {
private let didScroll: CGRect -> ()

@@ -64,13 +71,12 @@ private class ScrollViewElement: Element {
return view
}

private override func addRealizedChildView(childView: ViewType, selfView: ViewType) {
let scrollVew = selfView as! FewScrollView
scrollVew.scrollView.documentView = childView
private override func createRealizedElement(view: ViewType?, parent: RealizedElement?) -> RealizedElement {
return RealizedScrollViewElement(element: self, view: view, parent: parent)
}

private override func realize() -> RealizedElement {
let realizedElement = super.realize()
private override func realize(parent: RealizedElement?) -> RealizedElement {
let realizedElement = super.realize(parent)

let scrollView = realizedElement.view as! FewScrollView
let documentView = scrollView.scrollView.documentView as! NSView
17 changes: 5 additions & 12 deletions Few-Mac/TableView.swift
Original file line number Diff line number Diff line change
@@ -21,21 +21,14 @@ private class FewListCell: NSTableCellView {
if element.canDiff(realizedElement.element) {
element.applyDiff(realizedElement.element, realizedSelf: realizedElement)
} else {
realizedElement.element.derealize()
realizedElement.view.removeFromSuperview()
realizedElement.remove()

let newRealizedElement = element.realize()
newRealizedElement.view.autoresizingMask = .ViewWidthSizable | .ViewHeightSizable
newRealizedElement.view.frame = bounds
addSubview(newRealizedElement.view)
let parent = RealizedElement(element: Element(), view: self, parent: nil)
self.realizedElement = element.realize(parent)
}
} else {
let newRealizedElement = element.realize()
newRealizedElement.view.autoresizingMask = .ViewWidthSizable | .ViewHeightSizable
newRealizedElement.view.frame = bounds
addSubview(newRealizedElement.view)

realizedElement = newRealizedElement
let parent = RealizedElement(element: Element(), view: self, parent: nil)
realizedElement = element.realize(parent)
}
}

22 changes: 14 additions & 8 deletions Few-iOS/ScrollView.swift
Original file line number Diff line number Diff line change
@@ -29,6 +29,14 @@ private class FewScrollView: UIScrollView, UIScrollViewDelegate {
}
}

private class RealizedScrollViewElement: RealizedElement {
private override func addRealizedViewForChild(child: RealizedElement) {
let scrollView = view as! FewScrollView
scrollView.subviews.first?.removeFromSuperview()
scrollView.addSubview <^> child.view
}
}

private class ScrollViewElement: Element {
private let didScroll: CGRect -> ()

@@ -41,15 +49,13 @@ private class ScrollViewElement: Element {
private override func createView() -> ViewType {
return FewScrollView(frame: frame, didScroll: didScroll)
}

private override func createRealizedElement(view: ViewType?, parent: RealizedElement?) -> RealizedElement {
return RealizedScrollViewElement(element: self, view: view, parent: parent)
}

private override func addRealizedChildView(childView: ViewType, selfView: ViewType) {
let scrollView = selfView as! FewScrollView
scrollView.subviews.first?.removeFromSuperview()
scrollView.addSubview(childView)
}

private override func realize() -> RealizedElement {
let realizedElement = super.realize()
private override func realize(parent: RealizedElement?) -> RealizedElement {
let realizedElement = super.realize(parent)

let scrollView = realizedElement.view as! FewScrollView
if let element = children.first {
17 changes: 6 additions & 11 deletions Few-iOS/TableView.swift
Original file line number Diff line number Diff line change
@@ -20,19 +20,14 @@ private class FewListCell: UITableViewCell {
if element.canDiff(realizedElement.element) {
element.applyDiff(realizedElement.element, realizedSelf: realizedElement)
} else {
realizedElement.element.derealize()
realizedElement.view.removeFromSuperview()

let newRealizedElement = element.realize()
newRealizedElement.view.frame = bounds
addSubview(newRealizedElement.view)
realizedElement.remove()

let parent = RealizedElement(element: Element(), view: self, parent: nil)
self.realizedElement = element.realize(parent)
}
} else {
let newRealizedElement = element.realize()
newRealizedElement.view.frame = bounds
addSubview(newRealizedElement.view)

realizedElement = newRealizedElement
let parent = RealizedElement(element: Element(), view: self, parent: nil)
realizedElement = element.realize(parent)
}
}
}
4 changes: 2 additions & 2 deletions Few-iOS/iOS.swift
Original file line number Diff line number Diff line change
@@ -17,6 +17,6 @@ internal func compareAndSetAlpha(view: UIView, alpha: CGFloat) {
}
}

internal func configureViewToAutoresize(view: ViewType) {
view.autoresizingMask = .FlexibleWidth | .FlexibleHeight
internal func configureViewToAutoresize(view: ViewType?) {
view?.autoresizingMask = .FlexibleWidth | .FlexibleHeight
}
38 changes: 17 additions & 21 deletions FewCore/Component.swift
Original file line number Diff line number Diff line change
@@ -40,6 +40,8 @@ public class Component<S>: Element {
/// Is the component a root?
private var root = false

private var parent: RealizedElement?

private var frameChangedTrampoline = TargetActionTrampoline()

/// Initializes the component with its initial state. The render function
@@ -80,11 +82,10 @@ public class Component<S>: Element {
}

final private func realizeNewRoot(newRoot: Element) {
let realized = newRoot.realize()
let realized = newRoot.realize(parent)

configureViewToAutoresize(realized.view)

realizedRoot?.view.removeFromSuperview()
realizedRoot = realized
}

@@ -93,7 +94,7 @@ public class Component<S>: Element {
newRoot.frame = frame

let node = newRoot.assembleLayoutNode()
var layout: Layout!
let layout: Layout
if root {
layout = node.layout(maxWidth: frame.size.width)
} else {
@@ -112,13 +113,8 @@ public class Component<S>: Element {
if newRoot.canDiff(rootElement) {
newRoot.applyDiff(rootElement, realizedSelf: realizedRoot)
} else {
let superview = realizedRoot!.view.superview!
rootElement.derealize()

realizedRoot?.remove()
realizeNewRoot(newRoot)
superview.addSubview(realizedRoot!.view)

newRoot.elementDidRealize(realizedRoot!)
}

componentDidRender()
@@ -169,14 +165,12 @@ public class Component<S>: Element {
public func addToView(hostView: ViewType) {
root = true
frame = hostView.bounds
performInitialRenderIfNeeded()
realizeRootIfNeeded()
hostView.addSubview(realizedRoot!.view)
rootElement?.elementDidRealize(realizedRoot!)
let parent = RealizedElement(element: self, view: hostView, parent: nil)
realize(parent)

#if os(OSX)
hostView.postsFrameChangedNotifications = true
realizedRoot!.view.autoresizesSubviews = false
realizedRoot!.view?.autoresizesSubviews = false

frameChangedTrampoline.action = { [weak self] in
if let strongSelf = self {
@@ -289,9 +283,9 @@ public class Component<S>: Element {
public override func applyDiff(old: Element, realizedSelf: RealizedElement?) {
super.applyDiff(old, realizedSelf: realizedSelf)

// Use `unsafeBitCast` instead of `as` to avoid a runtime crash.
let oldComponent = unsafeBitCast(old, Component.self)
let oldComponent = old as! Component

parent = oldComponent.parent
root = oldComponent.root
state = oldComponent.state
rootElement = oldComponent.rootElement
@@ -300,20 +294,22 @@ public class Component<S>: Element {
renderNewRoot()
}

public override func realize() -> RealizedElement {
public override func realize(parent: RealizedElement?) -> RealizedElement {
self.parent = parent

performInitialRenderIfNeeded()
realizeRootIfNeeded()
return RealizedElement(element: self, view: realizedRoot!.view)
return super.realize(parent)
}

public override func derealize() {
componentWillDerealize()

rootElement?.derealize()
realizedRoot?.remove()
realizedRoot = nil
rootElement = nil

realizedRoot?.view.removeFromSuperview()
realizedRoot = nil
parent = nil

componentDidDerealize()
}
90 changes: 42 additions & 48 deletions FewCore/Element.swift
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@

import Foundation
import CoreGraphics
import SwiftBox

public var LogDiff = false

@@ -34,17 +33,15 @@ public class Element {

// On OS X we have to reverse our children since the default coordinate
// system is flipped.
#if os(OSX)
public var children: [Element] {
didSet {
#if os(OSX)
if direction == .Column {
children = children.reverse()
}
#endif
}
}
#else
public var children: [Element]
#endif

#if os(OSX)
public var direction: Direction {
@@ -107,7 +104,7 @@ public class Element {
/// should call super before doing their own diffing.
public func applyDiff(old: Element, realizedSelf: RealizedElement?) {
if LogDiff {
println("*** Diffing \(reflect(self).summary)")
println("*** Diffing \(self)")
}

let view = realizedSelf?.view
@@ -119,8 +116,8 @@ public class Element {
compareAndSetAlpha(view, alpha)
}

if frame != old.frame {
view?.frame = frame.integerRect
if viewFrame != old.viewFrame {
view?.frame = viewFrame
}

realizedSelf?.element = self
@@ -133,14 +130,11 @@ public class Element {
}

for child in childrenDiff.remove {
child.element.derealize()
realizedSelf.removeRealizedChild(child)
child.remove()
}

for child in childrenDiff.add {
let realizedChild = child.realize()
realizedSelf.addRealizedChild(realizedChild, index: indexOfObject(children, child))
child.elementDidRealize(realizedChild)
let realizedChild = child.realize(realizedSelf)
}

for child in childrenDiff.diff {
@@ -150,49 +144,55 @@ public class Element {
}

private final func printChildDiff(diff: ElementListDiff, old: Element) {
println("**** old: \(old.children)")
println("**** new: \(children)")
if old.children.count == 0 && children.count == 0 { return }

let oldChildren = old.children.map { "\($0.dynamicType)" }
println("**** old: \(oldChildren)")

let diffs: [String] = diff.diff.map {
let existing = $0.existing.element
let replacement = $0.replacement
return "\(replacement) => \(existing)"
let newChildren = children.map { "\($0.dynamicType)" }
println("**** new: \(newChildren)")

for d in diff.diff {
println("**** applying \(d.replacement.dynamicType) => \(d.existing.element.dynamicType)")
}
println("**** diffing \(diffs)")

println("**** removing \(diff.remove)")
println("**** adding \(diff.add)")
let removing = diff.remove.map { "\($0.element.dynamicType)" }
println("**** removing \(removing)")

let adding = diff.add.map { "\($0.dynamicType)" }
println("**** adding \(adding)")
println()
}

public func createView() -> ViewType {
return ViewType(frame: frame)
public func createView() -> ViewType? {
return nil
}

var viewFrame: CGRect {
return frame.integerRect
}

public func createRealizedElement(view: ViewType?, parent: RealizedElement?) -> RealizedElement {
return RealizedElement(element: self, view: view, parent: parent)
}

/// Realize the element.
internal func realize() -> RealizedElement {
public func realize(parent: RealizedElement?) -> RealizedElement {
let view = createView()
view.frame = frame.integerRect
view?.frame = viewFrame

let realizedSelf = createRealizedElement(view, parent: parent)
parent?.addRealizedChild(realizedSelf, index: indexOfObject(children, self))

let realizedSelf = RealizedElement(element: self, view: view)
let realizedChildren = children.map { $0.realize() }
for child in realizedChildren {
realizedSelf.addRealizedChild(child, index: nil)
for child in children {
child.realize(realizedSelf)
}

return realizedSelf
}

internal func addRealizedChildView(childView: ViewType, selfView: ViewType) {
selfView.addSubview(childView)
}

/// Derealize the element.
public func derealize() {
for child in children {
child.derealize()
}
}
public func derealize() {}

internal func assembleLayoutNode() -> Node {
let childNodes = children.map { $0.assembleLayoutNode() }
@@ -233,18 +233,12 @@ public class Element {
}

public func elementDidRealize(realizedSelf: RealizedElement) {
// Tell our children first so that we still end up grabbing focus even
// if a child also has autofocus.
for child in realizedSelf.children {
child.element.elementDidRealize(child)
}

if autofocus {
let window = realizedSelf.view.window!
let window = realizedSelf.view?.window!
#if os(OSX)
window.makeFirstResponder(realizedSelf.view)
window?.makeFirstResponder(realizedSelf.view)
#else
realizedSelf.view.becomeFirstResponder()
realizedSelf.view?.becomeFirstResponder()
#endif
}
}
52 changes: 43 additions & 9 deletions FewCore/RealizedElement.swift
Original file line number Diff line number Diff line change
@@ -10,23 +10,24 @@ import Foundation

internal func indexOfObject<T: AnyObject>(array: [T], element: T) -> Int? {
for (i, e) in enumerate(array) {
// HAHA SWIFT WHY DOES POINTER EQUALITY NOT WORK
let ptr1 = Unmanaged<T>.passUnretained(element).toOpaque()
let ptr2 = Unmanaged<T>.passUnretained(e).toOpaque()
if ptr1 == ptr2 { return i }
if element === e { return i }
}

return nil
}

public class RealizedElement {
public var element: Element
public let view: ViewType
public let view: ViewType?
public weak var parent: RealizedElement?

internal var children: [RealizedElement] = []
private var frameOffset = CGPointZero

public init(element: Element, view: ViewType) {
public init(element: Element, view: ViewType?, parent: RealizedElement?) {
self.element = element
self.view = view
self.parent = parent
}

public func addRealizedChild(child: RealizedElement, index: Int?) {
@@ -36,12 +37,45 @@ public class RealizedElement {
children.append(child)
}

element.addRealizedChildView(child.view, selfView: view)
addRealizedViewForChild(child)
}

public func addRealizedViewForChild(child: RealizedElement) {
if child.view == nil {
child.element.elementDidRealize(child)
return
}

var parent: RealizedElement? = self
var offset = CGPointZero
while let currentParent = parent {
if currentParent.view != nil { break }

offset.x += currentParent.element.frame.origin.x + currentParent.frameOffset.x
offset.y += currentParent.element.frame.origin.y + currentParent.frameOffset.y
parent = currentParent.parent
}

child.view?.frame.origin.x += offset.x
child.view?.frame.origin.y += offset.y
child.frameOffset = offset
parent?.view?.addSubview(child.view!)
child.element.elementDidRealize(child)
}

public func removeRealizedChild(child: RealizedElement) {
child.view.removeFromSuperview()
public func remove() {
for child in children {
child.remove()
}

view?.removeFromSuperview()
element.derealize()

parent?.removeRealizedChild(self)
parent = nil
}

private final func removeRealizedChild(child: RealizedElement) {
if let index = indexOfObject(children, child) {
children.removeAtIndex(index)
}
18 changes: 12 additions & 6 deletions FewDemo-iOS/ViewController.swift
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ import Few
func renderCounter(component: Component<Int>, count: Int) -> Element {
let updateCounter = { component.updateState { $0 + 1 } }

return View()
return Element()
// The view itself should be centered.
.justification(.Center)
// The children should be centered in the view.
@@ -48,7 +48,9 @@ private func renderRow(row: Int) -> Element {
}

func renderTableView(component: Component<()>, state: ()) -> Element {
return TableView((1...100).map(renderRow), selectionChanged: println)
return TableView((1...100).map(renderRow), selectionChanged: println)
.flex(1)
.selfAlignment(.Stretch)
}

let TableViewDemo = { Component(initialState: (), render: renderTableView) }
@@ -101,14 +103,18 @@ func renderApp(component: Few.Component<AppState>, state: AppState) -> Element {
return Element()
.direction(.Column)
.children([
contentComponent
.margin(Edges(top: 20))
.flex(1),
Element()
.children([
contentComponent
])
.childAlignment(.Center)
.justification(.Center)
.flex(1),
Button(title: "Show me more!", action: showMore)
.width(200)
.margin(Edges(uniform: 10))
.selfAlignment(.Center)
])
])
}

func toggleDisplay(var state: AppState) -> AppState {
8 changes: 4 additions & 4 deletions FewDemo/Demo.swift
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ private func renderInput(component: Few.Component<LoginState>, label: String, se
input = Input(action: action).autofocus(true)
}

return View()
return Element()
.direction(.Row)
.padding(Edges(bottom: 4))
.children([
@@ -84,7 +84,7 @@ private func renderScrollView() -> Element {
}

private func renderRow(row: Int) -> Element {
return View()
return Element()
.direction(.Column)
.children([
Label("I am a banana.", textColor: NSColor.yellowColor(), font: NSFont.systemFontOfSize(18)),
@@ -115,7 +115,7 @@ private func renderLogin() -> Element {

private func renderThingy(count: Int) -> Element {
let even = count % 2 == 0
return (even ? Empty() : View(backgroundColor: NSColor.blueColor())).size(100, 50)
return (even ? Element() : View(backgroundColor: NSColor.blueColor())).size(100, 50)
}

typealias Demo = Demo_<()>
@@ -125,7 +125,7 @@ class Demo_<LOL>: Few.Component<()> {
}

override func render() -> Element {
return View()
return Element()
.justification(.Center)
.childAlignment(.Center)
.direction(.Column)
19 changes: 19 additions & 0 deletions FewDemo/MyPlayground.playground/Contents.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Playground - noun: a place where people can play

import Cocoa
import Few
import XCPlayground

let view = View(backgroundColor: NSColor.redColor())
.direction(.Column)
.justification(.FlexEnd)
.children([
Label("Bleh").size(100, 23),
Label("World").size(100, 23),
Button(title: "Yoo").margin(Edges(uniform: 4))
])
let component = Component(initialState: ()) { _, _ in view }

let host = NSView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
component.addToView(host)
host
5 changes: 1 addition & 4 deletions FewDemo/MyPlayground.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='3.0' sdk='macosx'>
<sections>
<code source-file-name='section-1.swift'/>
</sections>
<playground version='5.0' target-platform='osx'>
<timeline fileName='timeline.xctimeline'/>
</playground>
8 changes: 0 additions & 8 deletions FewDemo/MyPlayground.playground/section-1.swift

This file was deleted.

4 changes: 2 additions & 2 deletions FewDemo/TemperatureConverter.swift
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ private func f2c(f: CGFloat) -> CGFloat {
}

private func renderLabeledInput(label: String, value: String, autofocus: Bool, fn: String -> ()) -> Element {
return View()
return Element()
.direction(.Row)
.padding(Edges(bottom: 4))
.children([
@@ -56,7 +56,7 @@ class TemperatureConverter_<LOL>: Few.Component<ConverterState> {

override func render() -> Element {
let state = getState()
return View()
return Element()
.justification(.Center)
.childAlignment(.Center)
.direction(.Column)
19 changes: 8 additions & 11 deletions FewTests/DiffTests.swift
Original file line number Diff line number Diff line change
@@ -14,46 +14,43 @@ class DiffTests: QuickSpec {
override func spec() {
describe("diffElementLists") {
let button = Button(title: "Hi") {}
let view = button.realize()
let realizedButton = RealizedElement(element: button, children: [], view: view)

let label = Label("Hey")
let realizedButton = button.realize(nil)
let label = Label("Hey").size(100, 23)

it("should detect simple diffing") {
let diff = diffElementLists([realizedButton], [button])
let diff = diffElementLists([ realizedButton ], [ button ])
expect(diff.add.count).to(equal(0))
expect(diff.remove.count).to(equal(0))
expect(diff.diff.count).to(equal(1))
}

it("should detect replacement") {
let diff = diffElementLists([realizedButton], [label])
let diff = diffElementLists([ realizedButton ], [ label ])
expect(diff.add.count).to(equal(1))
expect(diff.remove.count).to(equal(1))
expect(diff.diff.count).to(equal(0))
}

it("should detect removal") {
let diff = diffElementLists([realizedButton], [])
let diff = diffElementLists([ realizedButton ], [])
expect(diff.add.count).to(equal(0))
expect(diff.remove.count).to(equal(1))
expect(diff.diff.count).to(equal(0))
}

it("should detect addition") {
let diff = diffElementLists([realizedButton], [button, label])
let diff = diffElementLists([ realizedButton ], [ button, label ])
expect(diff.add.count).to(equal(1))
expect(diff.remove.count).to(equal(0))
expect(diff.diff.count).to(equal(1))
}

it("should use keys to match even when position changes") {
let labelView = label.realize()
let realizedLabel = RealizedElement(element: label, children: [], view: labelView)
let realizedLabel = label.realize(nil)

let newLabel = Label("No.")
newLabel.key = "key"
let diff = diffElementLists([realizedButton, realizedLabel], [button, newLabel, label])
let diff = diffElementLists([ realizedButton, realizedLabel ], [ button, newLabel, label ])
expect(diff.add.count).to(equal(1))
expect(diff.remove.count).to(equal(0))
expect(diff.diff.count).to(equal(2))

0 comments on commit ce59bfd

Please sign in to comment.