Cacao: Add layout guides
This commit is contained in:
parent
f375d157b0
commit
f0fe1e4a3d
|
@ -1,5 +1,6 @@
|
|||
package net.shadowfacts.cacao
|
||||
|
||||
import net.shadowfacts.cacao.util.LayoutGuide
|
||||
import net.shadowfacts.cacao.view.View
|
||||
import no.birkett.kiwi.Variable
|
||||
|
||||
|
@ -9,9 +10,22 @@ import no.birkett.kiwi.Variable
|
|||
*
|
||||
* @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)"
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
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.
|
||||
* 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) {
|
||||
// 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
|
||||
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) {
|
||||
return@filter true
|
||||
}
|
||||
|
@ -247,6 +258,18 @@ open class View(): Responder {
|
|||
// 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.
|
||||
*/
|
||||
|
@ -283,6 +306,10 @@ open class View(): Responder {
|
|||
open fun wasAdded() {
|
||||
createInternalConstraints()
|
||||
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