More ViewController work
This commit is contained in:
parent
6c0150e79a
commit
91e40e346b
|
@ -1 +1 @@
|
||||||
Subproject commit c3541de87180a9de734fcabf681c904cb2e40368
|
Subproject commit 1cbaea53d207f1e16c6e5ee2e6bf6e3c1440ac44
|
|
@ -17,8 +17,15 @@ import java.util.*
|
||||||
* The Window owns the Kiwi [Solver] object used for layout by all of its views.
|
* The Window owns the Kiwi [Solver] object used for layout by all of its views.
|
||||||
*
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
|
*
|
||||||
|
* @param viewController The root view controller for this window.
|
||||||
*/
|
*/
|
||||||
class Window(val viewController: ViewController) {
|
class Window(
|
||||||
|
/**
|
||||||
|
* The root view controller for this window.
|
||||||
|
*/
|
||||||
|
val viewController: ViewController
|
||||||
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The screen that this window belongs to.
|
* The screen that this window belongs to.
|
||||||
|
@ -134,9 +141,8 @@ class Window(val viewController: ViewController) {
|
||||||
* Should be called after the view hierarchy is setup.
|
* Should be called after the view hierarchy is setup.
|
||||||
*/
|
*/
|
||||||
fun layout() {
|
fun layout() {
|
||||||
solver.updateVariables()
|
|
||||||
viewController.viewWillLayoutSubviews()
|
viewController.viewWillLayoutSubviews()
|
||||||
viewController.view.didLayout()
|
solver.updateVariables()
|
||||||
viewController.viewDidLayoutSubviews()
|
viewController.viewDidLayoutSubviews()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package net.shadowfacts.cacao.util
|
||||||
|
|
||||||
|
import no.birkett.kiwi.Constraint
|
||||||
|
import no.birkett.kiwi.Term
|
||||||
|
import no.birkett.kiwi.Variable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the variables used by this constraint.
|
||||||
|
*
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
fun Constraint.getVariables(): List<Variable> {
|
||||||
|
return expression.terms.map(Term::getVariable)
|
||||||
|
}
|
|
@ -62,7 +62,7 @@ class DialogView(
|
||||||
for (type in buttonTypes) {
|
for (type in buttonTypes) {
|
||||||
buttonStack!!.addArrangedSubview(Button(Label(type.localizedName)).apply {
|
buttonStack!!.addArrangedSubview(Button(Label(type.localizedName)).apply {
|
||||||
handler = {
|
handler = {
|
||||||
this@DialogView.buttonCallback(type, this@DialogView.window)
|
this@DialogView.buttonCallback(type, this@DialogView.window!!)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -75,8 +75,8 @@ class DialogView(
|
||||||
super.createInternalConstraints()
|
super.createInternalConstraints()
|
||||||
|
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
centerXAnchor equalTo window.centerXAnchor
|
centerXAnchor equalTo window!!.centerXAnchor
|
||||||
centerYAnchor equalTo window.centerYAnchor
|
centerYAnchor equalTo window!!.centerYAnchor
|
||||||
|
|
||||||
widthAnchor greaterThanOrEqualTo 175
|
widthAnchor greaterThanOrEqualTo 175
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,8 @@ class Label(
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
updateIntrinsicContentSize()
|
updateIntrinsicContentSize()
|
||||||
window.layout()
|
// todo: setNeedsLayout instead of force unwrapping window
|
||||||
|
window!!.layout()
|
||||||
}
|
}
|
||||||
private lateinit var lines: List<String>
|
private lateinit var lines: List<String>
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import net.shadowfacts.cacao.util.*
|
||||||
import net.shadowfacts.cacao.util.properties.ObservableLateInitProperty
|
import net.shadowfacts.cacao.util.properties.ObservableLateInitProperty
|
||||||
import no.birkett.kiwi.Constraint
|
import no.birkett.kiwi.Constraint
|
||||||
import no.birkett.kiwi.Solver
|
import no.birkett.kiwi.Solver
|
||||||
|
import java.lang.RuntimeException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +24,7 @@ open class View() {
|
||||||
* Not initialized until the root view in this hierarchy has been added to a hierarchy,
|
* Not initialized until the root view in this hierarchy has been added to a hierarchy,
|
||||||
* using it before that will throw a runtime exception.
|
* using it before that will throw a runtime exception.
|
||||||
*/
|
*/
|
||||||
lateinit var window: Window
|
var window: Window? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The constraint solver used by the [net.shadowfacts.cacao.Window] this view belongs to.
|
* The constraint solver used by the [net.shadowfacts.cacao.Window] this view belongs to.
|
||||||
|
@ -117,9 +118,8 @@ open class View() {
|
||||||
* This view's parent view. If `null`, this view is a top-level view in the [Window].
|
* This view's parent view. If `null`, this view is a top-level view in the [Window].
|
||||||
*/
|
*/
|
||||||
var superview: View? = null
|
var superview: View? = null
|
||||||
// _subviews is the internal, mutable object since we only want it to by mutated by the add/removeSubview methods
|
// _subviews is the internal, mutable object since we only want it to be mutated by the add/removeSubview methods
|
||||||
private val _subviews = LinkedList<View>()
|
private val _subviews = LinkedList<View>()
|
||||||
|
|
||||||
private var subviewsSortedByZIndex: List<View> = listOf()
|
private var subviewsSortedByZIndex: List<View> = listOf()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -172,6 +172,38 @@ open class View() {
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given view from this view's children and removes all constraints associated with it.
|
||||||
|
*
|
||||||
|
* @param view The view to removed as a child of this view.
|
||||||
|
* @throws RuntimeException If the given [view] is not a subview of this view.
|
||||||
|
*/
|
||||||
|
fun removeSubview(view: View) {
|
||||||
|
if (view.superview != this) {
|
||||||
|
throw RuntimeException("Cannot remove subview whose superview is not this view")
|
||||||
|
}
|
||||||
|
solver.constraints.filter { constraint ->
|
||||||
|
constraint.getVariables().any { it is LayoutVariable && it.owner == view }
|
||||||
|
}.forEach(solver::removeConstraint)
|
||||||
|
_subviews.remove(view)
|
||||||
|
subviewsSortedByZIndex = subviews.sortedBy(View::zIndex)
|
||||||
|
|
||||||
|
view.superview = null
|
||||||
|
// todo: does this need to be reset
|
||||||
|
// view.solver = null
|
||||||
|
view.window = null
|
||||||
|
|
||||||
|
// todo: is this necessary?
|
||||||
|
// view.wasRemoved()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes this view from its superview, if it has one.
|
||||||
|
*/
|
||||||
|
fun removeFromSuperview() {
|
||||||
|
superview?.removeSubview(this)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds all subviews that contain the given point.
|
* Finds all subviews that contain the given point.
|
||||||
*
|
*
|
||||||
|
|
|
@ -63,7 +63,8 @@ class DropdownButton<Value, ContentView: View>(
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
updateView(value, contentView)
|
updateView(value, contentView)
|
||||||
window.layout()
|
// todo: setNeedsLayout instead of force unwrapping window
|
||||||
|
window!!.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun wasAdded() {
|
override fun wasAdded() {
|
||||||
|
|
|
@ -4,21 +4,45 @@ import net.shadowfacts.cacao.Window
|
||||||
import net.shadowfacts.cacao.util.properties.ObservableLazyProperty
|
import net.shadowfacts.cacao.util.properties.ObservableLazyProperty
|
||||||
import net.shadowfacts.cacao.view.View
|
import net.shadowfacts.cacao.view.View
|
||||||
import net.shadowfacts.kiwidsl.dsl
|
import net.shadowfacts.kiwidsl.dsl
|
||||||
|
import java.lang.RuntimeException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The base Cacao View Controller class. A view controller is an object that owns and manages a [View].
|
||||||
|
*
|
||||||
|
* The view controller receives lifecycle callbacks for its view.
|
||||||
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class ViewController {
|
abstract class ViewController {
|
||||||
|
|
||||||
lateinit var window: Window
|
/**
|
||||||
|
* The window that contains this view controller.
|
||||||
|
* This property is not set until either:
|
||||||
|
* a) a [Window] is initialized with this VC as it's root view controller or
|
||||||
|
* b) this VC is added as a child of another view controller.
|
||||||
|
*/
|
||||||
|
var window: Window? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for creating layout constraints in the domain of this VC's window.
|
||||||
|
* This function is not usable until [window] is initialized.
|
||||||
|
*/
|
||||||
val createConstraints
|
val createConstraints
|
||||||
get() = window.solver::dsl
|
get() = window!!.solver::dsl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view that this View Controller has.
|
||||||
|
* This property is created by [loadView] and is not initialized before that method has been called.
|
||||||
|
*
|
||||||
|
* @see loadView
|
||||||
|
*/
|
||||||
lateinit var view: View
|
lateinit var view: View
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This VC's parent view controller. If `null`, this VC is the root view controller of its [window].
|
||||||
|
*/
|
||||||
var parent: ViewController? = null
|
var parent: ViewController? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
willMoveTo(value)
|
willMoveTo(value)
|
||||||
|
@ -26,65 +50,138 @@ abstract class ViewController {
|
||||||
didMoveTo(value)
|
didMoveTo(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// _children is the internal, mutable object since we only want it to be mutated by the embed/removeChild methods
|
||||||
private var _children = LinkedList<ViewController>()
|
private var _children = LinkedList<ViewController>()
|
||||||
|
/**
|
||||||
|
* The list of all the child VCs of this view controller.
|
||||||
|
* This list should never be mutated directly, only by the [embedChild]/[removeChild] methods.
|
||||||
|
*/
|
||||||
val children: List<ViewController> = _children
|
val children: List<ViewController> = _children
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method somehow loads a [View] and sets this VC's [view] property to it.
|
||||||
|
*
|
||||||
|
* This method should only be called by the framework. After the [view] property is set, the framework is
|
||||||
|
* responsible for initializing its [View.window]/[View.solver] properties and calling [View.wasAdded].
|
||||||
|
*/
|
||||||
abstract fun loadView()
|
abstract fun loadView()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called after the view is loaded, it's properties are initialized, and [View.wasAdded] has been
|
||||||
|
* called.
|
||||||
|
*/
|
||||||
open fun viewDidLoad() {}
|
open fun viewDidLoad() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called immediately before the [Window.solver] is going to solve constraints and update variables.
|
||||||
|
* If overridden, the superclass method must be called.
|
||||||
|
*/
|
||||||
open fun viewWillLayoutSubviews() {
|
open fun viewWillLayoutSubviews() {
|
||||||
children.forEach(ViewController::viewWillLayoutSubviews)
|
children.forEach(ViewController::viewWillLayoutSubviews)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called immediately after the [Window.solver] has solved constraints and variables have been updated.
|
||||||
|
* This method is responsible for invoking the VC's [View.didLayout] method.
|
||||||
|
* If overridden, the superclass method must be called.
|
||||||
|
*/
|
||||||
open fun viewDidLayoutSubviews() {
|
open fun viewDidLayoutSubviews() {
|
||||||
|
view.didLayout()
|
||||||
children.forEach(ViewController::viewDidLayoutSubviews)
|
children.forEach(ViewController::viewDidLayoutSubviews)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the VC's view has been added to the screen and is about to be displayed.
|
||||||
|
*/
|
||||||
open fun viewWillAppear() {
|
open fun viewWillAppear() {
|
||||||
children.forEach(ViewController::viewWillAppear)
|
children.forEach(ViewController::viewWillAppear)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called immediately after the VC's view has first been displayed on screen.
|
||||||
|
*/
|
||||||
open fun viewDidAppear() {
|
open fun viewDidAppear() {
|
||||||
children.forEach(ViewController::viewDidAppear)
|
children.forEach(ViewController::viewDidAppear)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before the view will disappear from the screen, either because the VC has been removed from it's parent/screen
|
||||||
|
* or because the [net.shadowfacts.cacao.CacaoScreen] has been closed.
|
||||||
|
*/
|
||||||
open fun viewWillDisappear() {
|
open fun viewWillDisappear() {
|
||||||
children.forEach(ViewController::viewWillDisappear)
|
children.forEach(ViewController::viewWillDisappear)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the view has disappeared from the screen.
|
||||||
|
*/
|
||||||
open fun viewDidDisappear() {
|
open fun viewDidDisappear() {
|
||||||
children.forEach(ViewController::viewDidDisappear)
|
children.forEach(ViewController::viewDidDisappear)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before the view controller's parent changes to the given new value.
|
||||||
|
*
|
||||||
|
* @param parent The new parent view controller.
|
||||||
|
*/
|
||||||
open fun willMoveTo(parent: ViewController?) {}
|
open fun willMoveTo(parent: ViewController?) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the view controller's parent has changed to the given new value.
|
||||||
|
*
|
||||||
|
* @param parent The new parent view controller.
|
||||||
|
*/
|
||||||
open fun didMoveTo(parent: ViewController?) {}
|
open fun didMoveTo(parent: ViewController?) {}
|
||||||
|
|
||||||
fun embedChild(viewController: ViewController, container: View = this.view) {
|
/**
|
||||||
|
* Embeds a child view controller in this VC.
|
||||||
|
*
|
||||||
|
* @param viewController The new child VC.
|
||||||
|
* @param container The view that will be used as the superview for the child VC's view. Defaults to this VC's [view].
|
||||||
|
* @param pinEdges Whether the edges of the child VC will be pinned (constrained to be equal to) the container's edges.
|
||||||
|
* Defaults to `true`.
|
||||||
|
*/
|
||||||
|
fun embedChild(viewController: ViewController, container: View = this.view, pinEdges: Boolean = true) {
|
||||||
viewController.parent = this
|
viewController.parent = this
|
||||||
viewController.window = window
|
viewController.window = window
|
||||||
_children.add(viewController)
|
_children.add(viewController)
|
||||||
viewController.loadView()
|
viewController.loadView()
|
||||||
|
|
||||||
container.addSubview(viewController.view)
|
container.addSubview(viewController.view)
|
||||||
createConstraints {
|
|
||||||
viewController.view.leftAnchor equalTo container.leftAnchor
|
if (pinEdges) {
|
||||||
viewController.view.rightAnchor equalTo container.rightAnchor
|
createConstraints {
|
||||||
viewController.view.topAnchor equalTo container.topAnchor
|
viewController.view.leftAnchor equalTo container.leftAnchor
|
||||||
viewController.view.bottomAnchor equalTo container.bottomAnchor
|
viewController.view.rightAnchor equalTo container.rightAnchor
|
||||||
|
viewController.view.topAnchor equalTo container.topAnchor
|
||||||
|
viewController.view.bottomAnchor equalTo container.bottomAnchor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewController.viewDidLoad()
|
viewController.viewDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given view controller
|
||||||
|
*
|
||||||
|
* @param viewController The child VC to remove from this view controller.
|
||||||
|
* @throws RuntimeException If the given [viewController] is not a child of this VC.
|
||||||
|
*/
|
||||||
fun removeChild(viewController: ViewController) {
|
fun removeChild(viewController: ViewController) {
|
||||||
|
if (viewController.parent != this) {
|
||||||
|
throw RuntimeException("Cannot remove child view controller whose parent is not this view controller")
|
||||||
|
}
|
||||||
|
|
||||||
viewController.parent = null
|
viewController.parent = null
|
||||||
_children.remove(viewController)
|
_children.remove(viewController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes this view controller from its parent, if it has one.
|
||||||
|
*/
|
||||||
fun removeFromParent() {
|
fun removeFromParent() {
|
||||||
parent?.removeChild(this)
|
parent?.removeChild(this)
|
||||||
|
view.removeFromSuperview()
|
||||||
// todo: remove view from superview
|
// todo: remove view from superview
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue