diff --git a/src/groovylight/geom.py b/src/groovylight/geom.py index d80d6ac..51e4e42 100644 --- a/src/groovylight/geom.py +++ b/src/groovylight/geom.py @@ -110,17 +110,17 @@ class DisplayRotation(Enum): Generally, prefer LEFTRIGHT/UPDOWN over other rotations. """ - LEFTRIGHT = 0 - UPDOWN = 1 - DOWNUP = 2 - RIGHTLEFT = 3 # why are you like this. + R0 = 0 + R90 = 1 + R180 = 2 + R270 = 3 # why are you like this. @dataclass(frozen=True) class DisplayString: """Internal class to represent a string of HUB75 displays. - position: (X,Y) coordinates of the local top-left of the display + position: (X,Y) screen coordinates of the top-left of the display. dimensions: (length, height) local-coordinate dimensions of the display. @@ -129,8 +129,7 @@ class DisplayString: position: Coord dimensions: DisplayDimensions - rotation: DisplayRotation - # TODO: encode muxing + rotation: DisplayRotation = DisplayRotation.R0 @property def bbox(self) -> BBox: @@ -140,14 +139,10 @@ class DisplayString: l = self.dimensions.length h = self.dimensions.height match self.rotation: - case DisplayRotation.LEFTRIGHT: + case DisplayRotation.R0 | DisplayRotation.R270: return BBox(Coord(x, y), Coord(x + l, y + h)) - case DisplayRotation.UPDOWN: - return BBox(Coord(x - h, y), Coord(x, y + l)) - case DisplayRotation.DOWNUP: - return BBox(Coord(x, y + l), Coord(x + h, y)) - case DisplayRotation.RIGHTLEFT: - return BBox(Coord(x - l, y - h), Coord(x, y)) + case DisplayRotation.R90 | DisplayRotation.R180: + return BBox(Coord(x, y), Coord(x + h, y + l)) def contains_pix(self, coord: Coord) -> bool: """Checks if the given coordinate is inside this display.""" @@ -157,6 +152,38 @@ class DisplayString: """Checks if the given BBox intersects with this display""" return self.bbox.intersects(box) + def translate_coord(self, pixnum, addr, mux): + """Helper function to translate string coordinates to screen coordinates""" + + assert mux < self.dimensions.mux, "Mux must be within the mux of the display" + + x = self.position.x + y = self.position.y + + addrshift = addr + (self.dimensions.height // self.dimensions.mux) * mux + # + match self.rotation: + case DisplayRotation.R0: + # x is linear + return { + "x": x + pixnum, + "y": y + addrshift, + } + case DisplayRotation.R90: + # x is height - addrshift + return { + "x": x + self.dimensions.height - addrshift - 1, + "y": y + pixnum, + } + case DisplayRotation.R180: + pass + case DisplayRotation.R270: + # x and y are both length - pixnum + return { + "x": x + self.dimensions.length - 1 - pixnum, + "y": y + addrshift, + } + class DisplayGeometry: """Represents a display based on several strings in different positions.""" @@ -185,7 +212,7 @@ class DisplayGeometry: return sum def add_string(self, s: DisplayString): - """Add a new string to the display. + """Add a new string to the display. When in strict mode, this method will throw an exception if this new string will overlap with an existing string. """ diff --git a/src/groovylight/tests/test_geom.py b/src/groovylight/tests/test_geom.py index fdba69c..9e7453f 100644 --- a/src/groovylight/tests/test_geom.py +++ b/src/groovylight/tests/test_geom.py @@ -1,4 +1,4 @@ -from ..geom import Coord, BBox +from ..geom import Coord, BBox, DisplayString, DisplayDimensions, DisplayRotation import pytest @@ -37,3 +37,21 @@ def test_bbox(): BBox(Coord(0, 0), Coord(1, 0)) with pytest.raises(RuntimeError): BBox(Coord(1, 1), Coord(0, 0)) + + +ds_testdata = [ + (DisplayRotation.R0, (0, 0, 0), {"x": 3, "y": 0}), + (DisplayRotation.R0, (40, 2, 0), {"x": 43, "y": 2}), + (DisplayRotation.R0, (40, 2, 1), {"x": 43, "y": 34}), + (DisplayRotation.R90, (40, 0, 0), {"x": 66, "y": 40}), + (DisplayRotation.R90, (120, 2, 0), {"x": 64, "y": 120}), +] + + +@pytest.mark.parametrize("rot, inp, expected", ds_testdata) +def test_displaystring(rot, inp, expected): + ds = DisplayString(Coord(3, 0), DisplayDimensions(128, 64), rot) + + res = ds.translate_coord(*inp) + + assert res == expected