]> git.mar77i.info Git - zenbook_gui/commitdiff
import vectors, shapes
authormar77i <mar77i@protonmail.ch>
Tue, 21 Jan 2025 17:02:12 +0000 (18:02 +0100)
committermar77i <mar77i@protonmail.ch>
Tue, 21 Jan 2025 17:02:12 +0000 (18:02 +0100)
vectors/__init__.py [new file with mode: 0644]
vectors/vectors.py [new file with mode: 0644]
zenbook_conf/shapes.py [new file with mode: 0644]

diff --git a/vectors/__init__.py b/vectors/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/vectors/vectors.py b/vectors/vectors.py
new file mode 100644 (file)
index 0000000..b3bf98f
--- /dev/null
@@ -0,0 +1,305 @@
+from itertools import chain
+from math import acos, atan2, ceil, cos, floor, pi, sin, sqrt, tau
+
+import pygame
+
+
+class Shape:
+    def fit(self, pos, unit):
+        raise NotImplementedError
+
+    def draw(self, surf, color):
+        pass
+
+
+class Rect(Shape):
+    def __init__(self, *args):
+        if len(args) == 2:
+            args = tuple(chain.from_iterable(args))
+        self.left, self.top, self.width, self.height = args
+
+    def fit(self, pos, unit):
+        klass = type(self)
+        if klass not in (Rect, Ellipse):
+            raise NotImplementedError
+        return klass(
+            (round(pos[0] + self.left * unit[0]), round(pos[1] + self.top * unit[1])),
+            (round(self.width * unit[0]), round(self.height * unit[1])),
+        )
+
+    def draw(self, surf, color):
+        pygame.draw.rect(
+            surf, color, pygame.Rect(self.left, self.top, self.width, self.height)
+        )
+
+
+class Ellipse(Rect):
+    def draw(self, surf, color):
+        pygame.draw.ellipse(
+            surf, color, pygame.Rect(self.left, self.top, self.width, self.height)
+        )
+
+
+class Polygon(Shape):
+    def __init__(self, points):
+        self.points = list(points)
+
+    def fit(self, pos, unit):
+        if type(self) != Polygon:
+            raise NotImplementedError
+        return Polygon(
+            [
+                (round(pos[0] + point[0] * unit[0]), round(pos[1] + point[1] * unit[1]))
+                for point in self.points
+            ]
+        )
+
+    def draw(self, surf, color):
+        pygame.draw.polygon(surf, color, self.points)
+
+
+class Circle(Shape):
+    def __init__(self, center, radius):
+        self.center = center
+        self.radius = radius
+
+    def fit(self, pos, unit):
+        if type(self) != Circle:
+            raise NotImplementedError
+        if unit[0] == unit[1]:
+            return Circle(
+                (
+                    pos[0] + self.center[0] * unit[0],
+                    pos[1] + self.center[1] * unit[1],
+                ),
+                self.radius * unit[0],
+            )
+        return Ellipse(
+            (
+                round(pos[0] + (self.center[0] - self.radius) * unit[0]),
+                round(pos[1] + (self.center[1] - self.radius) * unit[1]),
+            ),
+            (round(self.radius * 2 * unit[0]), round(self.radius * 2 * unit[1])),
+        )
+
+    def draw(self, surf, color):
+        pygame.draw.circle(surf, color, self.center, self.radius)
+
+
+class StrokeSquareLine(Polygon):
+    """
+    Rotate a rectangle along the direction in which we're drawing.
+    """
+    def __init__(self, p1, p2, width):
+        self.p1 = p1
+        self.p2 = p2
+        self.width = width
+        super().__init__(self.get_points())
+
+    def get_points(self):
+        p_rel = (self.p1[0] - self.p2[0], self.p1[1] - self.p2[1])
+        twice_distance = 2 * sqrt(p_rel[0] * p_rel[0] + p_rel[1] * p_rel[1])
+        if twice_distance:
+            back = (
+                p_rel[0] * self.width / twice_distance,
+                p_rel[1] * self.width / twice_distance,
+            )
+            back_sum, back_diff = back[0] + back[1], back[0] - back[1]
+        else:
+            back_sum, back_diff = -self.width / 2, self.width / 2
+        # left is aka: (back[1], -back[0])
+        # right is aka: (-back[1], back[0])
+        return [
+            (self.p1[0] + back_sum, self.p1[1] - back_diff),
+            (self.p1[0] + back_diff, self.p1[1] + back_sum),
+            (self.p2[0] - back_sum, self.p2[1] + back_diff),
+            (self.p2[0] - back_diff, self.p2[1] - back_sum),
+        ]
+
+    def fit(self, pos, unit):
+        if type(self) != StrokeSquareLine:
+            raise NotImplementedError
+        return StrokeSquareLine(
+            (pos[0] + self.p1[0] * unit[0], pos[1] + self.p1[1] * unit[1]),
+            (pos[0] + self.p2[0] * unit[0], pos[1] + self.p2[1] * unit[1]),
+            self.width * max(unit),
+        )
+
+
+class StrokeRoundLine(Polygon):
+    """
+    Enclose a rectangle fitted between two points with a regular, non-rotated n-gon
+    that should be visually indistinguishable with a half circle
+    """
+    def __init__(self, p1, p2, width):
+        self.p1 = p1
+        self.p2 = p2
+        self.width = width
+        super().__init__(self.get_points())
+
+    def get_points(self):
+        radius = self.width / 2
+        num_vertices = self.corners_needed_to_appear_round(radius)
+        delta = (self.p1[0] - self.p2[0], self.p1[1] - self.p2[1])
+        distance = sqrt(delta[0] * delta[0] + delta[1] * delta[1])
+        points = []
+        if distance == 0:
+            initial_corner = opposing_corner = num_vertices
+            back = (0, 0)
+        else:
+            initial_corner = ceil(
+                atan2(-delta[0], delta[1]) * num_vertices / tau
+            )
+            opposing_corner = (
+                floor(atan2(delta[0], -delta[1]) * num_vertices / tau) - initial_corner
+            ) % num_vertices
+            back = (delta[0] * radius / distance, delta[1] * radius / distance)
+            points.extend(
+                (
+                    (self.p2[0] + back[1], self.p2[1] - back[0]),
+                    (self.p1[0] + back[1], self.p1[1] - back[0]),
+                )
+            )
+        p = self.p1
+        for i in range(num_vertices):
+            angle = (i + initial_corner) * tau / num_vertices
+            points.append((p[0] + cos(angle) * radius, p[1] + sin(angle) * radius))
+            if i == opposing_corner:
+                points.extend(
+                    (
+                        (self.p1[0] - back[1], self.p1[1] + back[0]),
+                        (self.p2[0] - back[1], self.p2[1] + back[0]),
+                    )
+                )
+                p = self.p2
+        return points
+
+    @staticmethod
+    def corners_needed_to_appear_round(radius, threshold=.5):
+        return ceil(pi / acos(1 - threshold / radius))
+
+    def fit(self, pos, unit):
+        if type(self) != StrokeRoundLine:
+            raise NotImplementedError
+        return StrokeRoundLine(
+            (pos[0] + self.p1[0] * unit[0], pos[1] + self.p1[1] * unit[1]),
+            (pos[0] + self.p2[0] * unit[0], pos[1] + self.p2[1] * unit[1]),
+            self.width * max(unit),
+        )
+
+
+class Shapes(Shape):
+    def __init__(self, shapes):
+        self.shapes = list(shapes)
+
+    def fit(self, pos, unit):
+        if type(self) != Shapes:
+            raise NotImplementedError
+        return Shapes([s.fit(pos, unit) for s in self.shapes])
+
+    def draw(self, surf, color):
+        for shape in self.shapes:
+            shape.draw(surf, color)
+
+
+class StrokePath:
+    def __init__(self, points, closed, width):
+        self.points = list(points)
+        self.closed = bool(closed)
+        self.width = width
+
+    @property
+    def shapes(self):
+        if not self.points:
+            return
+        if self.closed:
+            yield StrokeRoundLine(
+                self.points[-1],
+                self.points[0],
+                self.width,
+            )
+        iter_points = iter(self.points)
+        old = next(iter_points)
+        for new in iter_points:
+            yield StrokeRoundLine(old, new, self.width)
+            old = new
+
+    def fit(self, pos, unit):
+        if type(self) != StrokePath:
+            raise NotImplementedError
+        return StrokePath(
+            [
+                (pos[0] + point[0] * unit[0], pos[1] + point[1] * unit[1])
+                for point in self.points
+            ],
+            self.closed,
+            self.width,
+        )
+
+    draw = Shapes.draw
+
+
+class StrokeCircle(StrokePath):
+    def __init__(self, center, radius, width):
+        self.center = center
+        self.radius = radius
+        super().__init__([], True, width)
+        self.points.extend(self.get_points())
+
+    def get_points(self):
+        num_vertices = StrokeRoundLine.corners_needed_to_appear_round(self.radius)
+        return [
+            (
+                self.center[0] + cos(a) * self.radius,
+                self.center[1] + sin(a) * self.radius,
+            )
+            for a in (tau * i / num_vertices for i in range(num_vertices))
+        ]
+
+    def fit(self, pos, unit):
+        if type(self) != StrokeCircle:
+            raise NotImplementedError
+        return StrokeCircle(
+            (pos[0] + self.center[0] * unit[0], pos[1] + self.center[1] * unit[1]),
+            self.radius * max(unit),
+            self.width * max(unit),
+        )
+
+
+class StrokeCircleSegment(StrokeCircle):
+    def __init__(self, center, radius, start_angle, end_angle, width):
+        self.start_angle = start_angle
+        self.end_angle = end_angle
+        super().__init__(center, radius, width)
+        self.closed = False
+
+    def get_points(self):
+        return [
+            (self.center[0] + cos(a) * self.radius, self.center[1] + sin(a) * self.radius)
+            for a in self.get_angle_segments(
+                self.start_angle,
+                self.end_angle,
+                StrokeRoundLine.corners_needed_to_appear_round((self.radius + self.width / 2)),
+            )
+        ]
+
+    def get_angle_segments(self, start_angle, end_angle, num_vertices):
+        start_angle, end_angle = sorted((start_angle, end_angle))
+        diff_angle = end_angle - start_angle
+        for i in range(num_vertices):
+            a = tau * i / num_vertices
+            if a >= diff_angle:
+                yield end_angle
+                break
+            yield start_angle + a
+
+    def fit(self, pos, unit):
+        if type(self) != StrokeCircleSegment:
+            raise NotImplementedError
+        return StrokeCircleSegment(
+            (pos[0] + self.center[0] * unit[0], pos[1] + self.center[1] * unit[1]),
+            self.radius * max(unit),
+            self.start_angle,
+            self.end_angle,
+            self.width * max(unit),
+        )
diff --git a/zenbook_conf/shapes.py b/zenbook_conf/shapes.py
new file mode 100644 (file)
index 0000000..4d7be4b
--- /dev/null
@@ -0,0 +1,112 @@
+from math import cos, pi, sin, tau
+
+from vectors import (
+    Polygon, Rect, Shapes, StrokeCircleSegment, StrokeRoundLine, StrokeSquareLine
+)
+
+
+laptop_single = Shapes(
+    [
+        Rect((2, 3), (16.4, 1)),
+        Rect((2, 4), (1, 8.1)),
+        Rect((17.4, 4), (1, 8.1)),
+        Rect((2, 12.1), (16.4, 1)),
+        Polygon([(2, 13.1), (18.4, 13.1), (22.4, 18.1), (6, 18.1)]),
+    ]
+)
+laptop_double = Shapes(
+    [
+        Rect((2, 3), (16.4, 1)),
+        Rect((2, 4), (1, 8.1)),
+        Rect((17.4, 4), (1, 8.1)),
+        Rect((2, 12.1), (16.4, 1)),
+        Polygon([(2, 13.1), (3, 13.1), (6.2, 17.1), (5.2, 17.1)]),
+        Polygon([(17.4, 13.1), (18.4, 13.1), (21.6, 17.1), (20.4, 17.1)]),
+        Polygon([(5.2, 17.1), (21.6, 17.1), (22.4, 18.1), (6, 18.1)]),
+    ]
+)
+laptop_vertical = Shapes(
+    [
+        Rect((0.5, 1), (17.75, 1)),
+        Rect((0.5, 2), (1, 14.4)),
+        Rect((9, 2), (1, 14.4)),
+        Rect((17.25, 2), (1, 14.4)),
+        Rect((0.5, 16.4), (17.75, 1)),
+        Polygon([(2, 17.4), (6, 22.4), (20.75, 22.4), (16.75, 17.4)]),
+    ]
+)
+FINGER_RADIUS = 1.25
+touchscreen = Shapes(
+    [
+        StrokeCircleSegment((12, 16), 5, 0, tau * 3 / 8, 1),
+        StrokeRoundLine(
+            (12 + cos(tau * 3 / 8) * 5, 16 + sin(tau * 3 / 8) * 5),
+            (
+                4 + cos(tau * 3 / 8) * FINGER_RADIUS,
+                13 + sin(tau * 3 / 8) * FINGER_RADIUS,
+            ),
+            1,
+        ),
+        StrokeCircleSegment(
+            (4, 13), FINGER_RADIUS, tau * 3 / 8, tau * 7 / 8, 1,
+        ),
+        StrokeRoundLine(
+            (
+                4 + cos(tau * 7 / 8) * FINGER_RADIUS,
+                13 + sin(tau * 7 / 8) * FINGER_RADIUS,
+            ),
+            (7, 13.5),
+            1,
+        ),
+        StrokeRoundLine((7, 13.5), (7, 6), 1),
+        StrokeCircleSegment((8.25, 6), FINGER_RADIUS, pi, tau, 1),
+        StrokeRoundLine((9.5, 6), (9.5, 11), 1),
+        StrokeCircleSegment(
+            (11, 11.5), FINGER_RADIUS, tau * 5 / 8, tau * 7 / 8, 1,
+        ),
+        StrokeCircleSegment(
+            (13.25, 12), FINGER_RADIUS, tau * 5 / 8, tau * 7 / 8, 1,
+        ),
+        StrokeCircleSegment(
+            (15.5, 12.5), FINGER_RADIUS, tau * 5 / 8, tau, 1,
+        ),
+        StrokeRoundLine((16.75, 12.5), (17, 16), 1),
+        StrokeCircleSegment((8.25, 6), 3, pi, tau, 1),
+        StrokeCircleSegment((8.25, 6), 5, pi, tau, 1),
+    ]
+)
+stylus = Shapes(
+    [
+        StrokeCircleSegment((3, 3), 1.5, tau * 3 / 8, tau * 7 / 8, 1),
+        StrokeRoundLine(
+            (3 + cos(tau * 3 / 8) * 1.5, 3 + sin(tau * 3 / 8) * 1.5),
+            (16 + cos(tau * 3 / 8) * 1.5, 16 + sin(tau * 3 / 8) * 1.5),
+            1,
+        ),
+        StrokeRoundLine(
+            (3 + cos(tau * 7 / 8) * 1.5, 3 + sin(tau * 7 / 8) * 1.5),
+            (16 + cos(tau * 7 / 8) * 1.5, 16 + sin(tau * 7 / 8) * 1.5),
+            1,
+        ),
+        StrokeRoundLine(
+            (16 + cos(tau * 3 / 8) * 1.5, 16 + sin(tau * 3 / 8) * 1.5),
+            (18, 18),
+            1,
+        ),
+        StrokeRoundLine(
+            (16 + cos(tau * 7 / 8) * 1.5, 16 + sin(tau * 7 / 8) * 1.5),
+            (18, 18),
+            1,
+        ),
+        StrokeRoundLine((11, 11), (12, 12), 1),
+    ]
+)
+bluetooth = Shapes(
+    [
+        StrokeSquareLine((10, 2), (15, 6), 1.5),
+        StrokeSquareLine((15, 6), (5, 14), 1.5),
+        StrokeSquareLine((10, 2.75), (10, 18.25), 1.5),
+        StrokeSquareLine((10, 19), (15, 15), 1.5),
+        StrokeSquareLine((15, 15), (5, 7), 1.5),
+    ]
+)