diff --git a/External/SwiftBox b/External/SwiftBox index 1e6608b..0b9eb01 160000 --- a/External/SwiftBox +++ b/External/SwiftBox @@ -1 +1 @@ -Subproject commit 1e6608b7911877ba7a39758ab383119f879dc682 +Subproject commit 0b9eb010b567854ad9e86e08731083d5da72d72d diff --git a/Few-Mac/Mac.swift b/Few-Mac/Mac.swift index 67459a5..a82813b 100644 --- a/Few-Mac/Mac.swift +++ b/Few-Mac/Mac.swift @@ -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 } diff --git a/Few-Mac/QuickLook.swift b/Few-Mac/QuickLook.swift index b703537..c7c3c05 100644 --- a/Few-Mac/QuickLook.swift +++ b/Few-Mac/QuickLook.swift @@ -10,7 +10,7 @@ import Foundation extension Element { public func debugQuickLookObject() -> AnyObject? { - let realizedSelf = realize() + let realizedSelf = realize(nil) return realizedSelf.view } diff --git a/Few-Mac/ScrollView.swift b/Few-Mac/ScrollView.swift index d9d1eea..7de7097 100644 --- a/Few-Mac/ScrollView.swift +++ b/Few-Mac/ScrollView.swift @@ -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 diff --git a/Few-Mac/TableView.swift b/Few-Mac/TableView.swift index b5e89d9..5618dc3 100644 --- a/Few-Mac/TableView.swift +++ b/Few-Mac/TableView.swift @@ -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) } } diff --git a/Few-iOS/ScrollView.swift b/Few-iOS/ScrollView.swift index e783b92..61bc5ea 100644 --- a/Few-iOS/ScrollView.swift +++ b/Few-iOS/ScrollView.swift @@ -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 { diff --git a/Few-iOS/TableView.swift b/Few-iOS/TableView.swift index f109d71..b072fde 100644 --- a/Few-iOS/TableView.swift +++ b/Few-iOS/TableView.swift @@ -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) } } } diff --git a/Few-iOS/iOS.swift b/Few-iOS/iOS.swift index 7027d6e..d7c8ccf 100644 --- a/Few-iOS/iOS.swift +++ b/Few-iOS/iOS.swift @@ -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 } diff --git a/FewCore/Component.swift b/FewCore/Component.swift index bd7ca17..307f24c 100644 --- a/FewCore/Component.swift +++ b/FewCore/Component.swift @@ -40,6 +40,8 @@ public class Component: 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: 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: 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: 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: 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: 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: 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() } diff --git a/FewCore/Element.swift b/FewCore/Element.swift index e44f01a..7b1b04e 100644 --- a/FewCore/Element.swift +++ b/FewCore/Element.swift @@ -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 } } diff --git a/FewCore/RealizedElement.swift b/FewCore/RealizedElement.swift index 59f345e..c5be17e 100644 --- a/FewCore/RealizedElement.swift +++ b/FewCore/RealizedElement.swift @@ -10,10 +10,7 @@ import Foundation internal func indexOfObject(array: [T], element: T) -> Int? { for (i, e) in enumerate(array) { - // HAHA SWIFT WHY DOES POINTER EQUALITY NOT WORK - let ptr1 = Unmanaged.passUnretained(element).toOpaque() - let ptr2 = Unmanaged.passUnretained(e).toOpaque() - if ptr1 == ptr2 { return i } + if element === e { return i } } return nil @@ -21,12 +18,16 @@ internal func indexOfObject(array: [T], element: T) -> Int? { 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) } diff --git a/FewDemo-iOS/ViewController.swift b/FewDemo-iOS/ViewController.swift index 4e8873e..429ffaf 100644 --- a/FewDemo-iOS/ViewController.swift +++ b/FewDemo-iOS/ViewController.swift @@ -12,7 +12,7 @@ import Few func renderCounter(component: Component, 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, 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 { diff --git a/FewDemo/Demo.swift b/FewDemo/Demo.swift index 5ddfe15..ead21c3 100644 --- a/FewDemo/Demo.swift +++ b/FewDemo/Demo.swift @@ -31,7 +31,7 @@ private func renderInput(component: Few.Component, 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_: Few.Component<()> { } override func render() -> Element { - return View() + return Element() .justification(.Center) .childAlignment(.Center) .direction(.Column) diff --git a/FewDemo/MyPlayground.playground/Contents.swift b/FewDemo/MyPlayground.playground/Contents.swift new file mode 100644 index 0000000..92c24a6 --- /dev/null +++ b/FewDemo/MyPlayground.playground/Contents.swift @@ -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 diff --git a/FewDemo/MyPlayground.playground/contents.xcplayground b/FewDemo/MyPlayground.playground/contents.xcplayground index 4937636..06828af 100644 --- a/FewDemo/MyPlayground.playground/contents.xcplayground +++ b/FewDemo/MyPlayground.playground/contents.xcplayground @@ -1,7 +1,4 @@ - - - - + \ No newline at end of file diff --git a/FewDemo/MyPlayground.playground/section-1.swift b/FewDemo/MyPlayground.playground/section-1.swift deleted file mode 100644 index 2cfb749..0000000 --- a/FewDemo/MyPlayground.playground/section-1.swift +++ /dev/null @@ -1,8 +0,0 @@ -// Playground - noun: a place where people can play - -import Cocoa -import Few -import XCPlayground - -let view = View(backgroundColor: NSColor.redColor()).size(100, 100) -view.ql diff --git a/FewDemo/TemperatureConverter.swift b/FewDemo/TemperatureConverter.swift index 9243e43..97dd543 100644 --- a/FewDemo/TemperatureConverter.swift +++ b/FewDemo/TemperatureConverter.swift @@ -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_: Few.Component { override func render() -> Element { let state = getState() - return View() + return Element() .justification(.Center) .childAlignment(.Center) .direction(.Column) diff --git a/FewTests/DiffTests.swift b/FewTests/DiffTests.swift index 3ae0d59..0692ea2 100644 --- a/FewTests/DiffTests.swift +++ b/FewTests/DiffTests.swift @@ -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))