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())
|
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
|
package net.shadowfacts.cacao.view
|
||||||
|
|
||||||
import com.mojang.blaze3d.platform.GlStateManager
|
|
||||||
import net.shadowfacts.kiwidsl.dsl
|
import net.shadowfacts.kiwidsl.dsl
|
||||||
import net.shadowfacts.cacao.LayoutVariable
|
import net.shadowfacts.cacao.LayoutVariable
|
||||||
import net.shadowfacts.cacao.geometry.*
|
import net.shadowfacts.cacao.geometry.*
|
||||||
import net.shadowfacts.cacao.util.Color
|
import net.shadowfacts.cacao.util.*
|
||||||
import net.shadowfacts.cacao.util.LowestCommonAncestor
|
|
||||||
import net.shadowfacts.cacao.util.MouseButton
|
|
||||||
import net.shadowfacts.cacao.util.RenderHelper
|
|
||||||
import no.birkett.kiwi.Constraint
|
import no.birkett.kiwi.Constraint
|
||||||
import no.birkett.kiwi.Solver
|
import no.birkett.kiwi.Solver
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -18,7 +14,7 @@ import java.util.*
|
||||||
*
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
open class View {
|
open class View() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -60,13 +56,25 @@ open class View {
|
||||||
val centerYAnchor = LayoutVariable(this, "centerY")
|
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).
|
* Whether this view uses constraint-based layout.
|
||||||
* Not initialized until [didLayout] called.
|
* 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.
|
* 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
|
lateinit var bounds: Rect
|
||||||
|
|
||||||
|
@ -102,6 +110,11 @@ open class View {
|
||||||
*/
|
*/
|
||||||
val subviews: List<View> = _subviews
|
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.
|
* 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.
|
* If overridden, the super-class method must be called.
|
||||||
*/
|
*/
|
||||||
open fun createInternalConstraints() {
|
open fun createInternalConstraints() {
|
||||||
|
if (!usesConstraintBasedLayout) return
|
||||||
|
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
rightAnchor equalTo (leftAnchor + widthAnchor)
|
rightAnchor equalTo (leftAnchor + widthAnchor)
|
||||||
bottomAnchor equalTo (topAnchor + heightAnchor)
|
bottomAnchor equalTo (topAnchor + heightAnchor)
|
||||||
|
@ -172,7 +187,7 @@ open class View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateIntrinsicContentSizeConstraints(old: Size?, new: Size?) {
|
private fun updateIntrinsicContentSizeConstraints(old: Size?, new: Size?) {
|
||||||
if (!this::solver.isInitialized) return
|
if (!usesConstraintBasedLayout || !this::solver.isInitialized) return
|
||||||
|
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
solver.removeConstraint(intrinsicContentSizeWidthConstraint!!)
|
solver.removeConstraint(intrinsicContentSizeWidthConstraint!!)
|
||||||
|
@ -193,11 +208,13 @@ open class View {
|
||||||
open fun didLayout() {
|
open fun didLayout() {
|
||||||
subviews.forEach(View::didLayout)
|
subviews.forEach(View::didLayout)
|
||||||
|
|
||||||
|
if (usesConstraintBasedLayout) {
|
||||||
val superviewLeft = superview?.leftAnchor?.value ?: 0.0
|
val superviewLeft = superview?.leftAnchor?.value ?: 0.0
|
||||||
val superviewTop = superview?.topAnchor?.value ?: 0.0
|
val superviewTop = superview?.topAnchor?.value ?: 0.0
|
||||||
frame = Rect(leftAnchor.value - superviewLeft, topAnchor.value - superviewTop, widthAnchor.value, heightAnchor.value)
|
frame = Rect(leftAnchor.value - superviewLeft, topAnchor.value - superviewTop, widthAnchor.value, heightAnchor.value)
|
||||||
bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value)
|
bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to draw this view.
|
* Called to draw this view.
|
||||||
|
|
|
@ -21,12 +21,8 @@ class CoordinateConversionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testConvertToParent() {
|
fun testConvertToParent() {
|
||||||
val a = window.addView(View().apply {
|
val a = window.addView(View(Rect(0.0, 0.0, 100.0, 100.0)))
|
||||||
frame = Rect(0.0, 0.0, 100.0, 100.0)
|
val b = a.addSubview(View(Rect(25.0, 25.0, 50.0, 50.0)))
|
||||||
})
|
|
||||||
val b = a.addSubview(View().apply {
|
|
||||||
frame = 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(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))
|
assertEquals(Point(75.0, 75.0), b.convert(Point(50.0, 50.0), to = a))
|
||||||
|
@ -35,15 +31,9 @@ class CoordinateConversionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testConvertToSibling() {
|
fun testConvertToSibling() {
|
||||||
val root = window.addView(View().apply {
|
val root = window.addView(View(Rect(0.0, 0.0, 200.0, 200.0)))
|
||||||
frame = 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)))
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
assertEquals(Point(-50.0, -50.0), a.convert(Point(0.0, 0.0), to = b))
|
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))
|
assertEquals(Point(100.0, 100.0), b.convert(Point(50.0, 50.0), to = a))
|
||||||
|
@ -52,21 +42,11 @@ class CoordinateConversionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testConvertBetweenSubtrees() {
|
fun testConvertBetweenSubtrees() {
|
||||||
val root = window.addView(View().apply {
|
val root = window.addView(View(Rect(0.0, 0.0, 200.0, 100.0)))
|
||||||
frame = 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 a = root.addSubview(View().apply {
|
val c = a.addSubview(View(Rect(0.0, 0.0, 50.0, 50.0)))
|
||||||
frame = Rect(0.0, 0.0, 100.0, 100.0)
|
val d = b.addSubview(View(Rect(0.0, 0.0, 50.0, 50.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)
|
|
||||||
})
|
|
||||||
|
|
||||||
assertEquals(Point(-100.0, 0.0), c.convert(Point(0.0, 0.0), to = b))
|
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))
|
assertEquals(Point(-50.0, 50.0), c.convert(Point(50.0, 50.0), to = d))
|
||||||
|
@ -75,12 +55,8 @@ class CoordinateConversionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testConvertBetweenTopLevelViews() {
|
fun testConvertBetweenTopLevelViews() {
|
||||||
val a = window.addView(View().apply {
|
val a = window.addView(View(Rect(0.0, 0.0, 100.0, 100.0)))
|
||||||
frame = Rect(0.0, 0.0, 100.0, 100.0)
|
val b = window.addView(View(Rect(100.0, 100.0, 100.0, 100.0)))
|
||||||
})
|
|
||||||
val b = window.addView(View().apply {
|
|
||||||
frame = 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(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))
|
assertEquals(Point(200.0, 200.0), b.convert(Point(100.0, 100.0), to = a))
|
||||||
|
@ -89,18 +65,10 @@ class CoordinateConversionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testConvertBetweenTopLevelSubtrees() {
|
fun testConvertBetweenTopLevelSubtrees() {
|
||||||
val a = window.addView(View().apply {
|
val a = window.addView(View(Rect(0.0, 0.0, 100.0, 100.0)))
|
||||||
frame = 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 b = window.addView(View().apply {
|
val d = b.addSubview(View(Rect(25.0, 25.0, 50.0, 50.0)))
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
assertEquals(Point(-50.0, -50.0), c.convert(Point(50.0, 50.0), to = d))
|
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))
|
assertEquals(Point(100.0, 100.0), d.convert(Point(0.0, 0.0), to = c))
|
||||||
|
|
|
@ -24,11 +24,7 @@ class ViewClickTests {
|
||||||
@Test
|
@Test
|
||||||
fun testClickInsideRootView() {
|
fun testClickInsideRootView() {
|
||||||
val mouse = CompletableFuture<Point>()
|
val mouse = CompletableFuture<Point>()
|
||||||
window.addView(object: View() {
|
window.addView(object: View(Rect(50.0, 50.0, 100.0, 100.0)) {
|
||||||
init {
|
|
||||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
||||||
mouse.complete(point)
|
mouse.complete(point)
|
||||||
}
|
}
|
||||||
|
@ -41,11 +37,7 @@ class ViewClickTests {
|
||||||
@Test
|
@Test
|
||||||
fun testClickOutsideRootView() {
|
fun testClickOutsideRootView() {
|
||||||
val clicked = CompletableFuture<Boolean>()
|
val clicked = CompletableFuture<Boolean>()
|
||||||
window.addView(object: View() {
|
window.addView(object: View(Rect(50.0, 50.0, 100.0, 100.0)) {
|
||||||
init {
|
|
||||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
||||||
clicked.complete(true)
|
clicked.complete(true)
|
||||||
}
|
}
|
||||||
|
@ -58,14 +50,8 @@ class ViewClickTests {
|
||||||
@Test
|
@Test
|
||||||
fun testClickInsideNestedView() {
|
fun testClickInsideNestedView() {
|
||||||
val mouse = CompletableFuture<Point>()
|
val mouse = CompletableFuture<Point>()
|
||||||
val root = window.addView(View().apply {
|
val root = window.addView(View(Rect(50.0, 50.0, 100.0, 100.0)))
|
||||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
root.addSubview(object: View(Rect(25.0, 25.0, 50.0, 50.0)) {
|
||||||
})
|
|
||||||
root.addSubview(object: View() {
|
|
||||||
init {
|
|
||||||
frame = Rect(25.0, 25.0, 50.0, 50.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
||||||
mouse.complete(point)
|
mouse.complete(point)
|
||||||
}
|
}
|
||||||
|
@ -78,14 +64,8 @@ class ViewClickTests {
|
||||||
@Test
|
@Test
|
||||||
fun testClickOutsideNestedView() {
|
fun testClickOutsideNestedView() {
|
||||||
val clicked = CompletableFuture<Boolean>()
|
val clicked = CompletableFuture<Boolean>()
|
||||||
val root = window.addView(View().apply {
|
val root = window.addView(View(Rect(50.0, 50.0, 100.0, 100.0)))
|
||||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
root.addSubview(object: View(Rect(25.0, 25.0, 50.0, 50.0)) {
|
||||||
})
|
|
||||||
root.addSubview(object: View() {
|
|
||||||
init {
|
|
||||||
frame = Rect(25.0, 25.0, 50.0, 50.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
|
||||||
clicked.complete(true)
|
clicked.complete(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,12 +32,7 @@ class ViewHoverTests {
|
||||||
@Test
|
@Test
|
||||||
fun testHoverRootView() {
|
fun testHoverRootView() {
|
||||||
val point = CompletableFuture<Point>()
|
val point = CompletableFuture<Point>()
|
||||||
window.addView(object: View() {
|
window.addView(object: View(Rect(50.0, 50.0, 100.0, 100.0)) {
|
||||||
init {
|
|
||||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
|
||||||
bounds = Rect(0.0, 0.0, 100.0, 100.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawContent(mouse: Point, delta: Float) {
|
override fun drawContent(mouse: Point, delta: Float) {
|
||||||
point.complete(mouse)
|
point.complete(mouse)
|
||||||
}
|
}
|
||||||
|
@ -50,16 +45,8 @@ class ViewHoverTests {
|
||||||
@Test
|
@Test
|
||||||
fun testHoverNestedView() {
|
fun testHoverNestedView() {
|
||||||
val point = CompletableFuture<Point>()
|
val point = CompletableFuture<Point>()
|
||||||
val root = window.addView(View().apply {
|
val root = window.addView(View(Rect(50.0, 50.0, 100.0, 100.0)))
|
||||||
frame = Rect(50.0, 50.0, 100.0, 100.0)
|
root.addSubview(object: View(Rect(25.0, 25.0, 50.0, 50.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawContent(mouse: Point, delta: Float) {
|
override fun drawContent(mouse: Point, delta: Float) {
|
||||||
point.complete(mouse)
|
point.complete(mouse)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue