ASMR/src/main/kotlin/net/shadowfacts/cacao/view/StackView.kt

212 lines
8.8 KiB
Kotlin

package net.shadowfacts.cacao.view
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.cacao.LayoutVariable
import net.shadowfacts.cacao.geometry.Axis
import net.shadowfacts.cacao.geometry.AxisPosition
import net.shadowfacts.cacao.geometry.AxisPosition.*
import no.birkett.kiwi.Constraint
import java.util.*
/**
* A view that lays out its children in a stack along either the horizontal for vertical axes.
* This view does not have any content of its own.
*
* Only arranged subviews will be laid out in the stack mode, normal subviews must perform their own layout.
*
* @author shadowfacts
* @param axis The primary axis that this stack lays out its children along.
* @param distribution The mode by which this stack lays out its children along the axis perpendicular to the
* primary [axis].
*/
class StackView(
val axis: Axis,
val distribution: Distribution = Distribution.FILL,
val spacing: Double = 0.0
): View() {
// the internal mutable, list of arranged subviews
private val _arrangedSubviews = LinkedList<View>()
/**
* The list of arranged subviews belonging to this stack view.
* This list should never be mutated directly, only be calling the [addArrangedSubview]/[removeArrangedSubview]
* methods.
*/
val arrangedSubviews: List<View> = _arrangedSubviews
private var leadingConnection: Constraint? = null
private var trailingConnection: Constraint? = null
private var arrangedSubviewConnections = mutableListOf<Constraint>()
/**
* Adds an arranged subview to this view.
* Arranged subviews are laid out according to the stack. If you wish to add a subview that is laid out separately,
* use the normal [addSubview] method.
*
* @param view The view to add.
* @param index The index in this stack to add the view at.
* By default, adds the view to the end of the stack.
* @return The view that was added, as a convenience.
*/
fun <T: View> addArrangedSubview(view: T, index: Int = arrangedSubviews.size): T {
addSubview(view)
_arrangedSubviews.add(index, view)
addConstraintsForArrangedView(view, index)
return view
}
private fun addConstraintsForArrangedView(view: View, index: Int) {
if (index == 0) {
if (leadingConnection != null) {
solver.removeConstraint(leadingConnection)
}
solver.dsl {
leadingConnection = anchor(LEADING) equalTo anchor(LEADING, view)
}
}
if (index == arrangedSubviews.size - 1) {
if (trailingConnection != null) {
solver.removeConstraint(trailingConnection)
}
solver.dsl {
trailingConnection = anchor(TRAILING, view) equalTo anchor(TRAILING)
}
}
if (arrangedSubviews.size > 1) {
solver.dsl {
val previous = arrangedSubviews.getOrNull(index - 1)
val next = arrangedSubviews.getOrNull(index + 1)
if (next != null) {
arrangedSubviewConnections.add(index, anchor(TRAILING, view) equalTo (anchor(LEADING, next) + spacing))
}
if (previous != null) {
arrangedSubviewConnections.add(index - 1, anchor(TRAILING, previous) equalTo (anchor(LEADING, view) - spacing))
}
}
}
solver.dsl {
when (distribution) {
Distribution.LEADING ->
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
Distribution.TRAILING ->
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
Distribution.FILL -> {
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
}
Distribution.CENTER ->
perpAnchor(CENTER) equalTo perpAnchor(CENTER, view)
}
}
}
private fun anchor(position: AxisPosition, view: View = this): LayoutVariable {
return view.getAnchor(axis, position)
}
private fun perpAnchor(position: AxisPosition, view: View = this): LayoutVariable {
return view.getAnchor(axis.perpendicular, position)
}
/**
* Defines the modes of how content is distributed in a stack view along the perpendicular axis (i.e. the
* non-primary axis).
*
* ASCII-art examples are shown below in a stack view with the primary axis [Axis.VERTICAL].
*/
enum class Distribution {
/**
* The leading edges of arranged subviews are pinned to the leading edge of the stack view.
* ```
* ┌─────────────────────────────┐
* │┌─────────────┐ │
* ││ │ │
* ││ │ │
* ││ │ │
* │└─────────────┘ │
* │┌─────────┐ │
* ││ │ │
* ││ │ │
* ││ │ │
* │└─────────┘ │
* │┌───────────────────────────┐│
* ││ ││
* ││ ││
* ││ ││
* │└───────────────────────────┘│
* └─────────────────────────────┘
* ```
*/
LEADING,
/**
* The centers of the arranged subviews are pinned to the center of the stack view.
* ```
* ┌─────────────────────────────┐
* │ ┌─────────────┐ │
* │ │ │ │
* │ │ │ │
* │ │ │ │
* │ └─────────────┘ │
* │ ┌─────────┐ │
* │ │ │ │
* │ │ │ │
* │ │ │ │
* │ └─────────┘ │
* │┌───────────────────────────┐│
* ││ ││
* ││ ││
* ││ ││
* │└───────────────────────────┘│
* └─────────────────────────────┘
* ```
*/
CENTER,
/**
* The trailing edges of arranged subviews are pinned to the leading edge of the stack view.
* ```
* ┌─────────────────────────────┐
* │ ┌─────────────┐│
* │ │ ││
* │ │ ││
* │ │ ││
* │ └─────────────┘│
* │ ┌─────────┐│
* │ │ ││
* │ │ ││
* │ │ ││
* │ └─────────┘│
* │┌───────────────────────────┐│
* ││ ││
* ││ ││
* ││ ││
* │└───────────────────────────┘│
* └─────────────────────────────┘
* ```
*/
TRAILING,
/**
* The arranged subviews fill the perpendicular axis of the stack view.
* ```
* ┌─────────────────────────────┐
* │┌───────────────────────────┐│
* ││ ││
* ││ ││
* ││ ││
* │└───────────────────────────┘│
* │┌───────────────────────────┐│
* ││ ││
* ││ ││
* ││ ││
* │└───────────────────────────┘│
* │┌───────────────────────────┐│
* ││ ││
* ││ ││
* ││ ││
* │└───────────────────────────┘│
* └─────────────────────────────┘
* ```
*/
FILL
}
}