Compare commits
No commits in common. "184d98bcddd448a68cd63ed202017cea64acc114" and "83bd29ecb8f03b737c71e9cbb947ff20dc59d235" have entirely different histories.
184d98bcdd
...
83bd29ecb8
|
@ -7,8 +7,6 @@ package net.shadowfacts.cacao.geometry
|
||||||
*/
|
*/
|
||||||
data class Rect(val left: Double, val top: Double, val width: Double, val height: Double) {
|
data class Rect(val left: Double, val top: Double, val width: Double, val height: Double) {
|
||||||
|
|
||||||
constructor(origin: Point, size: Size): this(origin.x, origin.y, size.width, size.height)
|
|
||||||
|
|
||||||
val right: Double by lazy {
|
val right: Double by lazy {
|
||||||
left + width
|
left + width
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
package net.shadowfacts.cacao.util
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.NoSuchElementException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A linear time algorithm for finding the lowest common ancestor of two nodes in a graph.
|
|
||||||
* Based on https://stackoverflow.com/a/6342546/4731558
|
|
||||||
*
|
|
||||||
* Works be finding the path from each node back to the root node.
|
|
||||||
* The LCA will then be the node after which the paths diverge.
|
|
||||||
*
|
|
||||||
* @author shadowfacts
|
|
||||||
*/
|
|
||||||
object LowestCommonAncestor {
|
|
||||||
|
|
||||||
fun <Node> find(node1: Node, node2: Node, parent: Node.() -> Node?): Node? {
|
|
||||||
@Suppress("NAME_SHADOWING") var node1: Node? = node1
|
|
||||||
@Suppress("NAME_SHADOWING") var node2: Node? = node2
|
|
||||||
|
|
||||||
val parent1 = LinkedList<Node>()
|
|
||||||
while (node1 != null) {
|
|
||||||
parent1.push(node1)
|
|
||||||
node1 = node1.parent()
|
|
||||||
}
|
|
||||||
|
|
||||||
val parent2 = LinkedList<Node>()
|
|
||||||
while (node2 != null) {
|
|
||||||
parent2.push(node2)
|
|
||||||
node2 = node2.parent()
|
|
||||||
}
|
|
||||||
|
|
||||||
// paths don't converge on the same root element
|
|
||||||
if (parent1.first != parent2.first) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldNode: Node? = null
|
|
||||||
while (node1 == node2 && parent1.isNotEmpty() && parent2.isNotEmpty()) {
|
|
||||||
oldNode = node1
|
|
||||||
node1 = parent1.popOrNull()
|
|
||||||
node2 = parent2.popOrNull()
|
|
||||||
}
|
|
||||||
return if (node1 == node2) node1!!
|
|
||||||
else oldNode!!
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> LinkedList<T>.popOrNull(): T? {
|
|
||||||
return try {
|
|
||||||
pop()
|
|
||||||
} catch (e: NoSuchElementException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,11 @@ package net.shadowfacts.cacao.view
|
||||||
import com.mojang.blaze3d.platform.GlStateManager
|
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.Axis
|
||||||
|
import net.shadowfacts.cacao.geometry.AxisPosition
|
||||||
|
import net.shadowfacts.cacao.geometry.Rect
|
||||||
|
import net.shadowfacts.cacao.geometry.Size
|
||||||
import net.shadowfacts.cacao.util.Color
|
import net.shadowfacts.cacao.util.Color
|
||||||
import net.shadowfacts.cacao.util.LowestCommonAncestor
|
|
||||||
import net.shadowfacts.cacao.util.RenderHelper
|
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
|
||||||
|
@ -212,47 +214,4 @@ open class View {
|
||||||
*/
|
*/
|
||||||
open fun drawContent() {}
|
open fun drawContent() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given point in this view's coordinate system to the coordinate system of another view or the window.
|
|
||||||
*
|
|
||||||
* @param point The point to convert.
|
|
||||||
* @param to The view to convert to. If `null`, it will be converted to the window's coordinate system.
|
|
||||||
* @return The point in the coordinate system of the [to] view.
|
|
||||||
*/
|
|
||||||
fun convert(point: Point, to: View?): Point {
|
|
||||||
if (to != null) {
|
|
||||||
val ancestor = LowestCommonAncestor.find(this, to, View::superview)
|
|
||||||
@Suppress("NAME_SHADOWING") var point = point
|
|
||||||
|
|
||||||
// Convert up to the LCA
|
|
||||||
var view: View? = this
|
|
||||||
while (view != null && view != ancestor) {
|
|
||||||
point = Point(point.x + view.frame.left, point.y + view.frame.top)
|
|
||||||
view = view.superview
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert back down to the other view
|
|
||||||
view = to
|
|
||||||
while (view != null && view != ancestor) {
|
|
||||||
point = Point(point.x - view.frame.left, point.y - view.frame.top)
|
|
||||||
view = view.superview
|
|
||||||
}
|
|
||||||
|
|
||||||
return point
|
|
||||||
} else {
|
|
||||||
return Point(leftAnchor.value + point.x, topAnchor.value + point.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given rectangle in this view's coordinate system to the coordinate system of another view or the window.
|
|
||||||
*
|
|
||||||
* @param rect The rectangle to convert.
|
|
||||||
* @param to The view to convert to. If `null`, it will be converted to the window's coordinate system.
|
|
||||||
* @return The rectangle in the coordinate system of the [to] view.
|
|
||||||
*/
|
|
||||||
fun convert(rect: Rect, to: View?): Rect {
|
|
||||||
return Rect(convert(rect.origin, to), rect.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,105 +0,0 @@
|
||||||
package net.shadowfacts.cacao
|
|
||||||
|
|
||||||
import net.shadowfacts.cacao.geometry.Point
|
|
||||||
import net.shadowfacts.cacao.geometry.Rect
|
|
||||||
import net.shadowfacts.cacao.view.View
|
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author shadowfacts
|
|
||||||
*/
|
|
||||||
class PointConversionTests {
|
|
||||||
|
|
||||||
lateinit var window: Window
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun setup() {
|
|
||||||
window = Window()
|
|
||||||
}
|
|
||||||
|
|
||||||
@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)
|
|
||||||
})
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
@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)
|
|
||||||
})
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
@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)
|
|
||||||
})
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
@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)
|
|
||||||
})
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
@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)
|
|
||||||
})
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
package net.shadowfacts.cacao.util
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
|
||||||
import org.junit.jupiter.api.Assertions.assertNull
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author shadowfacts
|
|
||||||
*/
|
|
||||||
class LCATest {
|
|
||||||
|
|
||||||
class Node(val name: String, val parent: Node?)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDirectParent() {
|
|
||||||
val parent = Node("parent", null)
|
|
||||||
val child = Node("child", parent)
|
|
||||||
assertEquals(parent, LowestCommonAncestor.find(parent, child, Node::parent))
|
|
||||||
assertEquals(parent, LowestCommonAncestor.find(child, parent, Node::parent))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSiblings() {
|
|
||||||
val root = Node("root", null)
|
|
||||||
val a = Node("a", root)
|
|
||||||
val b = Node("b", root)
|
|
||||||
assertEquals(root, LowestCommonAncestor.find(a, b, Node::parent))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testBetweenSubtrees() {
|
|
||||||
// ┌────┐
|
|
||||||
// │root│
|
|
||||||
// └────┘
|
|
||||||
// ╱ ╲
|
|
||||||
// ╱ ╲
|
|
||||||
// ┌─┐ ┌─┐
|
|
||||||
// │A│ │B│
|
|
||||||
// └─┘ └─┘
|
|
||||||
// ╱ ╲ ╱ ╲
|
|
||||||
// ╱ ╲ ╱ ╲
|
|
||||||
// ┌─┐ ┌─┐┌─┐ ┌─┐
|
|
||||||
// │C│ │D││E│ │F│
|
|
||||||
// └─┘ └─┘└─┘ └─┘
|
|
||||||
val root = Node("root", null)
|
|
||||||
|
|
||||||
val a = Node("a", root)
|
|
||||||
val c = Node("c", a)
|
|
||||||
val d = Node("d", a)
|
|
||||||
|
|
||||||
val b = Node("b", root)
|
|
||||||
val e = Node("e", b)
|
|
||||||
val f = Node("f", b)
|
|
||||||
|
|
||||||
assertEquals(a, LowestCommonAncestor.find(c, d, Node::parent))
|
|
||||||
assertEquals(root, LowestCommonAncestor.find(c, b, Node::parent))
|
|
||||||
assertEquals(root, LowestCommonAncestor.find(d, e, Node::parent))
|
|
||||||
assertEquals(root, LowestCommonAncestor.find(c, root, Node::parent))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testBetweenDisjointTrees() {
|
|
||||||
val a = Node("a", null)
|
|
||||||
val b = Node("b", a)
|
|
||||||
|
|
||||||
val c = Node("c", null)
|
|
||||||
val d = Node("d", c)
|
|
||||||
|
|
||||||
assertNull(LowestCommonAncestor.find(a, d, Node::parent))
|
|
||||||
assertNull(LowestCommonAncestor.find(b, c, Node::parent))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue