Cacao: Add layout guides
This commit is contained in:
parent
f375d157b0
commit
f0fe1e4a3d
|
@ -1,5 +1,6 @@
|
||||||
package net.shadowfacts.cacao
|
package net.shadowfacts.cacao
|
||||||
|
|
||||||
|
import net.shadowfacts.cacao.util.LayoutGuide
|
||||||
import net.shadowfacts.cacao.view.View
|
import net.shadowfacts.cacao.view.View
|
||||||
import no.birkett.kiwi.Variable
|
import no.birkett.kiwi.Variable
|
||||||
|
|
||||||
|
@ -9,9 +10,22 @@ import no.birkett.kiwi.Variable
|
||||||
*
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class LayoutVariable(val owner: View, val property: String): Variable("LayoutVariable") {
|
class LayoutVariable(
|
||||||
|
val view: View?,
|
||||||
|
val layoutGuide: LayoutGuide?,
|
||||||
|
val property: String,
|
||||||
|
): Variable("LayoutVariable") {
|
||||||
|
|
||||||
override fun getName() = "$owner.$property"
|
constructor(view: View, property: String): this(view, null, property)
|
||||||
|
constructor(layoutGuide: LayoutGuide, property: String): this(null, layoutGuide, property)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if ((view == null) == (layoutGuide == null)) {
|
||||||
|
throw RuntimeException("LayoutVariable must be constructed with either a view or layout guide")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getName() = "${view ?: layoutGuide}.$property"
|
||||||
|
|
||||||
override fun toString() = "LayoutVariable(name=$name, value=$value)"
|
override fun toString() = "LayoutVariable(name=$name, value=$value)"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package net.shadowfacts.cacao.util
|
||||||
|
|
||||||
|
import net.shadowfacts.cacao.LayoutVariable
|
||||||
|
import net.shadowfacts.cacao.geometry.Rect
|
||||||
|
import net.shadowfacts.cacao.view.View
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A layout guide is a non-view object that represents a rectangular area for the purposes of constraint-based layout.
|
||||||
|
* It allows you to define complex layouts without needing empty container views.
|
||||||
|
*
|
||||||
|
* A layout guide is always owned by a view. The owning view's dimensions are not necessarily tied to the layout guide's.
|
||||||
|
*
|
||||||
|
* To create a layout guide, call [View.addLayoutGuide] on the owning view.
|
||||||
|
*
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class LayoutGuide(
|
||||||
|
val owningView: View,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val leftAnchor: LayoutVariable = LayoutVariable(this, "left")
|
||||||
|
val rightAnchor: LayoutVariable = LayoutVariable(this, "right")
|
||||||
|
val topAnchor: LayoutVariable = LayoutVariable(this, "top")
|
||||||
|
val bottomAnchor: LayoutVariable = LayoutVariable(this, "bottom")
|
||||||
|
val widthAnchor: LayoutVariable = LayoutVariable(this, "width")
|
||||||
|
val heightAnchor: LayoutVariable = LayoutVariable(this, "height")
|
||||||
|
val centerXAnchor: LayoutVariable = LayoutVariable(this, "centerX")
|
||||||
|
val centerYAnchor: LayoutVariable = LayoutVariable(this, "centerY")
|
||||||
|
|
||||||
|
val frame: Rect
|
||||||
|
get() = Rect(leftAnchor.value - owningView.leftAnchor.value, topAnchor.value - owningView.topAnchor.value, widthAnchor.value, heightAnchor.value)
|
||||||
|
|
||||||
|
}
|
|
@ -84,6 +84,17 @@ open class View(): Responder {
|
||||||
*/
|
*/
|
||||||
val centerYAnchor = LayoutVariable(this, "centerY")
|
val centerYAnchor = LayoutVariable(this, "centerY")
|
||||||
|
|
||||||
|
private val _layoutGuides = LinkedList<LayoutGuide>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All the layout guides attached to this view.
|
||||||
|
*
|
||||||
|
* To add a layout guide, call [addLayoutGuide].
|
||||||
|
*
|
||||||
|
* @see LayoutGuide
|
||||||
|
*/
|
||||||
|
val layoutGuides: List<LayoutGuide> = _layoutGuides
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this view uses constraint-based layout.
|
* Whether this view uses constraint-based layout.
|
||||||
* If `false`, the view's `frame` must be set manually and the layout anchors may not be used.
|
* If `false`, the view's `frame` must be set manually and the layout anchors may not be used.
|
||||||
|
@ -229,7 +240,7 @@ open class View(): Responder {
|
||||||
for (b in a + 1 until variables.size) {
|
for (b in a + 1 until variables.size) {
|
||||||
// if the variable views have no common ancestor after the removed view's superview is unset,
|
// if the variable views have no common ancestor after the removed view's superview is unset,
|
||||||
// the constraint crossed the this<->view boundary and should be removed
|
// the constraint crossed the this<->view boundary and should be removed
|
||||||
val ancestor = LowestCommonAncestor.find(variables[a].owner, variables[b].owner, View::superview)
|
val ancestor = LowestCommonAncestor.find(variables[a].viewOrLayoutGuideView, variables[b].viewOrLayoutGuideView, View::superview)
|
||||||
if (ancestor == null) {
|
if (ancestor == null) {
|
||||||
return@filter true
|
return@filter true
|
||||||
}
|
}
|
||||||
|
@ -247,6 +258,18 @@ open class View(): Responder {
|
||||||
// view.wasRemoved()
|
// view.wasRemoved()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a new layout guide with this view as its owner.
|
||||||
|
*/
|
||||||
|
fun addLayoutGuide(): LayoutGuide {
|
||||||
|
val guide = LayoutGuide(this)
|
||||||
|
_layoutGuides.add(guide)
|
||||||
|
if (hasSolver) {
|
||||||
|
guide.attachTo(solver)
|
||||||
|
}
|
||||||
|
return guide
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes this view from its superview, if it has one.
|
* Removes this view from its superview, if it has one.
|
||||||
*/
|
*/
|
||||||
|
@ -283,6 +306,10 @@ open class View(): Responder {
|
||||||
open fun wasAdded() {
|
open fun wasAdded() {
|
||||||
createInternalConstraints()
|
createInternalConstraints()
|
||||||
updateIntrinsicContentSizeConstraints(null, intrinsicContentSize)
|
updateIntrinsicContentSizeConstraints(null, intrinsicContentSize)
|
||||||
|
|
||||||
|
layoutGuides.forEach {
|
||||||
|
it.attachTo(solver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -463,3 +490,15 @@ open class View(): Responder {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun LayoutGuide.attachTo(solver: Solver) {
|
||||||
|
solver.dsl {
|
||||||
|
rightAnchor equalTo (leftAnchor + widthAnchor)
|
||||||
|
bottomAnchor equalTo (topAnchor + heightAnchor)
|
||||||
|
centerXAnchor equalTo (leftAnchor + widthAnchor / 2)
|
||||||
|
centerYAnchor equalTo (topAnchor + heightAnchor / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val LayoutVariable.viewOrLayoutGuideView: View
|
||||||
|
get() = view ?: layoutGuide!!.owningView
|
||||||
|
|
Loading…
Reference in New Issue