diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt index 3935e4a..b8364a8 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt @@ -7,6 +7,8 @@ package net.shadowfacts.cacao.geometry */ 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 { left + width } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt index e82b96f..dfe0f0c 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt @@ -3,11 +3,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.Axis -import net.shadowfacts.cacao.geometry.AxisPosition -import net.shadowfacts.cacao.geometry.Rect -import net.shadowfacts.cacao.geometry.Size +import net.shadowfacts.cacao.geometry.* import net.shadowfacts.cacao.util.Color +import net.shadowfacts.cacao.util.LowestCommonAncestor import net.shadowfacts.cacao.util.RenderHelper import no.birkett.kiwi.Constraint import no.birkett.kiwi.Solver @@ -214,4 +212,47 @@ open class View { */ 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, in the coordinate system of this view. + * @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, in the coordinate system of this view. + * @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) + } + } \ No newline at end of file diff --git a/src/test/kotlin/net/shadowfacts/cacao/CoordinateConversionTests.kt b/src/test/kotlin/net/shadowfacts/cacao/CoordinateConversionTests.kt new file mode 100644 index 0000000..45bd5e7 --- /dev/null +++ b/src/test/kotlin/net/shadowfacts/cacao/CoordinateConversionTests.kt @@ -0,0 +1,110 @@ +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 CoordinateConversionTests { + + 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)) + assertEquals(Rect(25.0, 25.0, 50.0, 50.0), b.convert(Rect(0.0, 0.0, 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)) + assertEquals(Rect(50.0, 50.0, 50.0, 50.0), b.convert(Rect(0.0, 0.0, 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)) + assertEquals(Rect(100.0, 0.0, 50.0, 50.0), d.convert(Rect(0.0, 0.0, 50.0, 50.0), to = c)) + } + + @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)) + assertEquals(Rect(100.0, 100.0, 100.0, 100.0), b.convert(Rect(0.0, 0.0, 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)) + assertEquals(Rect(100.0, 100.0, 50.0, 50.0), d.convert(Rect(0.0, 0.0, 50.0, 50.0), to = c)) + } + +} \ No newline at end of file