Add frame based layout
This commit is contained in:
parent
bee2eceb10
commit
bf7e7bd24c
|
@ -9,4 +9,8 @@ data class Point(val x: Double, val y: Double) {
|
|||
|
||||
constructor(x: Int, y: Int): this(x.toDouble(), y.toDouble())
|
||||
|
||||
companion object {
|
||||
val ORIGIN = Point(0.0, 0.0)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package net.shadowfacts.cacao.util
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class ObservableLateInitProperty<T: Any>(val observer: (T) -> Unit) {
|
||||
|
||||
lateinit var storage: T
|
||||
|
||||
operator fun getValue(thisRef: Any, property: KProperty<*>): T {
|
||||
return storage
|
||||
}
|
||||
|
||||
operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
|
||||
storage = value
|
||||
observer(value)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +1,9 @@
|
|||
package net.shadowfacts.cacao.view
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager
|
||||
import net.shadowfacts.kiwidsl.dsl
|
||||
import net.shadowfacts.cacao.LayoutVariable
|
||||
import net.shadowfacts.cacao.geometry.*
|
||||
import net.shadowfacts.cacao.util.Color
|
||||
import net.shadowfacts.cacao.util.LowestCommonAncestor
|
||||
import net.shadowfacts.cacao.util.MouseButton
|
||||
import net.shadowfacts.cacao.util.RenderHelper
|
||||
import net.shadowfacts.cacao.util.*
|
||||
import no.birkett.kiwi.Constraint
|
||||
import no.birkett.kiwi.Solver
|
||||
import java.util.*
|
||||
|
@ -18,7 +14,7 @@ import java.util.*
|
|||
*
|
||||
* @author shadowfacts
|
||||
*/
|
||||
open class View {
|
||||
open class View() {
|
||||
|
||||
/**
|
||||
* The constraint solver used by the [net.shadowfacts.cacao.Window] this view belongs to.
|
||||
|
@ -60,13 +56,25 @@ open class View {
|
|||
val centerYAnchor = LayoutVariable(this, "centerY")
|
||||
|
||||
/**
|
||||
* The rectangle for this view in the coordinate system of its superview view (or the window, if there is no superview).
|
||||
* Not initialized until [didLayout] called.
|
||||
* 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.
|
||||
* Note: some views (such as [StackView] require arranged subviews to use constraint based layout.
|
||||
*
|
||||
* Default is `true`.
|
||||
*/
|
||||
lateinit var frame: Rect
|
||||
var usesConstraintBasedLayout = true
|
||||
|
||||
/**
|
||||
* The rectangle for this view in the coordinate system of its superview view (or the window, if there is no superview).
|
||||
* If using constraint based layout, this property is not initialized until [didLayout] called.
|
||||
* Otherwise, this must be set manually.
|
||||
* Setting this property updates the [bounds].
|
||||
*/
|
||||
var frame: Rect by ObservableLateInitProperty { this.bounds = Rect(Point.ORIGIN, it.size) }
|
||||
/**
|
||||
* The rectangle for this view in its own coordinate system.
|
||||
* Not initialized until [didLayout] called.
|
||||
* If using constraint based layout, this property is not initialized until [didLayout] called.
|
||||
* Otherwise, this will be initialized when [frame] is set.
|
||||
*/
|
||||
lateinit var bounds: Rect
|
||||
|
||||
|
@ -102,6 +110,11 @@ open class View {
|
|||
*/
|
||||
val subviews: List<View> = _subviews
|
||||
|
||||
constructor(frame: Rect): this() {
|
||||
this.usesConstraintBasedLayout = false
|
||||
this.frame = frame
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for retrieve the anchor for a specific position on the given axis.
|
||||
*/
|
||||
|
@ -163,6 +176,8 @@ open class View {
|
|||
* If overridden, the super-class method must be called.
|
||||
*/
|
||||
open fun createInternalConstraints() {
|
||||
if (!usesConstraintBasedLayout) return
|
||||
|
||||
solver.dsl {
|
||||
rightAnchor equalTo (leftAnchor + widthAnchor)
|
||||
bottomAnchor equalTo (topAnchor + heightAnchor)
|
||||
|
@ -172,7 +187,7 @@ open class View {
|
|||
}
|
||||
|
||||
private fun updateIntrinsicContentSizeConstraints(old: Size?, new: Size?) {
|
||||
if (!this::solver.isInitialized) return
|
||||
if (!usesConstraintBasedLayout || !this::solver.isInitialized) return
|
||||
|
||||
if (old != null) {
|
||||
solver.removeConstraint(intrinsicContentSizeWidthConstraint!!)
|
||||
|
@ -193,10 +208,12 @@ open class View {
|
|||
open fun didLayout() {
|
||||
subviews.forEach(View::didLayout)
|
||||
|
||||
val superviewLeft = superview?.leftAnchor?.value ?: 0.0
|
||||
val superviewTop = superview?.topAnchor?.value ?: 0.0
|
||||
frame = Rect(leftAnchor.value - superviewLeft, topAnchor.value - superviewTop, widthAnchor.value, heightAnchor.value)
|
||||
bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value)
|
||||
if (usesConstraintBasedLayout) {
|
||||
val superviewLeft = superview?.leftAnchor?.value ?: 0.0
|
||||
val superviewTop = superview?.topAnchor?.value ?: 0.0
|
||||
frame = Rect(leftAnchor.value - superviewLeft, topAnchor.value - superviewTop, widthAnchor.value, heightAnchor.value)
|
||||
bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,12 +21,8 @@ class CoordinateConversionTests {
|
|||
|
||||
@Test
|
||||
fun testConvertToParent() {
|
||||
val a = window.addView(View().apply {
|
||||
frame = Rect(0.0, 0.0, 100.0, 100.0)
|
||||
})
|
||||
val b = a.addSubview(View().apply {
|
||||
frame = Rect(25.0, 25.0, 50.0, 50.0)
|
||||
})
|
||||
val a = window.addView(View(Rect(0.0, 0.0, 100.0, 100.0)))
|
||||
val b = a.addSubview(View(Rect(25.0, 25.0, 50.0, 50.0)))
|
||||
|
||||
assertEquals(Point(25.0, 25.0), b.convert(Point(0.0, 0.0), to = a))
|
||||
assertEquals(Point(75.0, 75.0), b.convert(Point(50.0, 50.0), to = a))
|
||||
|
@ -35,15 +31,9 @@ class CoordinateConversionTests {
|
|||
|
||||
@Test
|
||||
fun testConvertToSibling() {
|
||||
val root = window.addView(View().apply {
|
||||
frame = Rect(0.0, 0.0, 200.0, 200.0)
|
||||
})
|
||||
val a = root.addSubview(View().apply {
|
||||
frame = Rect(25.0, 25.0, 50.0, 50.0)
|
||||
})
|
||||
val b = root.addSubview(View().apply {
|
||||
frame = Rect(75.0, 75.0, 50.0, 50.0)
|
||||
})
|
||||
val root = window.addView(View(Rect(0.0, 0.0, 200.0, 200.0)))
|
||||
val a = root.addSubview(View(Rect(25.0, 25.0, 50.0, 50.0)))
|
||||
val b = root.addSubview(View(Rect(75.0, 75.0, 50.0, 50.0)))
|
||||
|
||||
assertEquals(Point(-50.0, -50.0), a.convert(Point(0.0, 0.0), to = b))
|
||||
assertEquals(Point(100.0, 100.0), b.convert(Point(50.0, 50.0), to = a))
|
||||
|
@ -52,21 +42,11 @@ class CoordinateConversionTests {
|
|||
|
||||
@Test
|
||||
fun testConvertBetweenSubtrees() {
|
||||
val root = window.addView(View().apply {
|
||||
frame = Rect(0.0, 0.0, 200.0, 100.0)
|
||||
})
|
||||
val a = root.addSubview(View().apply {
|
||||
frame = Rect(0.0, 0.0, 100.0, 100.0)
|
||||
})
|
||||
val b = root.addSubview(View().apply {
|
||||
frame = Rect(100.0, 0.0, 100.0, 100.0)
|
||||
})
|
||||
val c = a.addSubview(View().apply {
|
||||
frame = Rect(0.0, 0.0, 50.0, 50.0)
|
||||
})
|
||||
val d = b.addSubview(View().apply {
|
||||
frame = Rect(0.0, 0.0, 50.0, 50.0)
|
||||
})
|
||||
val root = window.addView(View(Rect(0.0, 0.0, 200.0, 100.0)))
|
||||
val a = root.addSubview(View(Rect(0.0, 0.0, 100.0, 100.0)))
|
||||
val b = root.addSubview(View(Rect(100.0, 0.0, 100.0, 100.0)))
|
||||
val c = a.addSubview(View(Rect(0.0, 0.0, 50.0, 50.0)))
|
||||
val d = b.addSubview(View(Rect(0.0, 0.0, 50.0, 50.0)))
|
||||
|
||||
assertEquals(Point(-100.0, 0.0), c.convert(Point(0.0, 0.0), to = b))
|
||||
assertEquals(Point(-50.0, 50.0), c.convert(Point(50.0, 50.0), to = d))
|
||||
|
@ -75,12 +55,8 @@ class CoordinateConversionTests {
|
|||
|
||||
@Test
|
||||
fun testConvertBetweenTopLevelViews() {
|
||||
val a = window.addView(View().apply {
|
||||
frame = Rect(0.0, 0.0, 100.0, 100.0)
|
||||
})
|
||||
val b = window.addView(View().apply {
|
||||
frame = Rect(100.0, 100.0, 100.0, 100.0)
|
||||
})
|
||||
val a = window.addView(View(Rect(0.0, 0.0, 100.0, 100.0)))
|
||||
val b = window.addView(View(Rect(100.0, 100.0, 100.0, 100.0)))
|
||||
|
||||
assertEquals(Point(0.0, 0.0), a.convert(Point(100.0, 100.0), to = b))
|
||||
assertEquals(Point(200.0, 200.0), b.convert(Point(100.0, 100.0), to = a))
|
||||
|
@ -89,18 +65,10 @@ class CoordinateConversionTests {
|
|||
|
||||
@Test
|
||||
fun testConvertBetweenTopLevelSubtrees() {
|
||||
val a = window.addView(View().apply {
|
||||
frame = Rect(0.0, 0.0, 100.0, 100.0)
|
||||
})
|
||||
val b = window.addView(View().apply {
|
||||
frame = Rect(100.0, 100.0, 100.0, 100.0)
|
||||
})
|
||||
val c = a.addSubview(View().apply {
|
||||
frame = Rect(25.0, 25.0, 50.0, 50.0)
|
||||
})
|
||||
val d = b.addSubview(View().apply {
|
||||
frame = Rect(25.0, 25.0, 50.0, 50.0)
|
||||
})
|
||||
val a = window.addView(View(Rect(0.0, 0.0, 100.0, 100.0)))
|
||||
val b = window.addView(View(Rect(100.0, 100.0, 100.0, 100.0)))
|
||||
val c = a.addSubview(View(Rect(25.0, 25.0, 50.0, 50.0)))
|
||||
val d = b.addSubview(View(Rect(25.0, 25.0, 50.0, 50.0)))
|
||||
|
||||
assertEquals(Point(-50.0, -50.0), c.convert(Point(50.0, 50.0), to = d))
|
||||
assertEquals(Point(100.0, 100.0), d.convert(Point(0.0, 0.0), to = c))
|
||||
|
|
|
@ -24,11 +24,7 @@ class ViewClickTests {
|
|||
@Test
|
||||
fun testClickInsideRootView() {
|
||||
val mouse = CompletableFuture<Point>()
|
||||
window.addView(object: View() {
|
||||
init {
|
||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
||||
}
|
||||
|
||||
window.addView(object: View(Rect(50.0, 50.0, 100.0, 100.0)) {
|
||||
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
||||
mouse.complete(point)
|
||||
}
|
||||
|
@ -41,11 +37,7 @@ class ViewClickTests {
|
|||
@Test
|
||||
fun testClickOutsideRootView() {
|
||||
val clicked = CompletableFuture<Boolean>()
|
||||
window.addView(object: View() {
|
||||
init {
|
||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
||||
}
|
||||
|
||||
window.addView(object: View(Rect(50.0, 50.0, 100.0, 100.0)) {
|
||||
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
||||
clicked.complete(true)
|
||||
}
|
||||
|
@ -58,14 +50,8 @@ class ViewClickTests {
|
|||
@Test
|
||||
fun testClickInsideNestedView() {
|
||||
val mouse = CompletableFuture<Point>()
|
||||
val root = window.addView(View().apply {
|
||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
||||
})
|
||||
root.addSubview(object: View() {
|
||||
init {
|
||||
frame = Rect(25.0, 25.0, 50.0, 50.0)
|
||||
}
|
||||
|
||||
val root = window.addView(View(Rect(50.0, 50.0, 100.0, 100.0)))
|
||||
root.addSubview(object: View(Rect(25.0, 25.0, 50.0, 50.0)) {
|
||||
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
||||
mouse.complete(point)
|
||||
}
|
||||
|
@ -78,14 +64,8 @@ class ViewClickTests {
|
|||
@Test
|
||||
fun testClickOutsideNestedView() {
|
||||
val clicked = CompletableFuture<Boolean>()
|
||||
val root = window.addView(View().apply {
|
||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
||||
})
|
||||
root.addSubview(object: View() {
|
||||
init {
|
||||
frame = Rect(25.0, 25.0, 50.0, 50.0)
|
||||
}
|
||||
|
||||
val root = window.addView(View(Rect(50.0, 50.0, 100.0, 100.0)))
|
||||
root.addSubview(object: View(Rect(25.0, 25.0, 50.0, 50.0)) {
|
||||
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
||||
clicked.complete(true)
|
||||
}
|
||||
|
|
|
@ -32,12 +32,7 @@ class ViewHoverTests {
|
|||
@Test
|
||||
fun testHoverRootView() {
|
||||
val point = CompletableFuture<Point>()
|
||||
window.addView(object: View() {
|
||||
init {
|
||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
||||
bounds = Rect(0.0, 0.0, 100.0, 100.0)
|
||||
}
|
||||
|
||||
window.addView(object: View(Rect(50.0, 50.0, 100.0, 100.0)) {
|
||||
override fun drawContent(mouse: Point, delta: Float) {
|
||||
point.complete(mouse)
|
||||
}
|
||||
|
@ -50,16 +45,8 @@ class ViewHoverTests {
|
|||
@Test
|
||||
fun testHoverNestedView() {
|
||||
val point = CompletableFuture<Point>()
|
||||
val root = window.addView(View().apply {
|
||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
||||
bounds = Rect(0.0, 0.0, 100.0, 100.0)
|
||||
})
|
||||
root.addSubview(object: View() {
|
||||
init {
|
||||
frame = Rect(25.0, 25.0, 50.0, 50.0)
|
||||
bounds = Rect(0.0, 0.0, 50.0, 50.0)
|
||||
}
|
||||
|
||||
val root = window.addView(View(Rect(50.0, 50.0, 100.0, 100.0)))
|
||||
root.addSubview(object: View(Rect(25.0, 25.0, 50.0, 50.0)) {
|
||||
override fun drawContent(mouse: Point, delta: Float) {
|
||||
point.complete(mouse)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue