+from .base import Shape, Shapes
+from .circle import Circle
+from .ellipse import Ellipse
+from .polygon import Polygon
+from .rect import Rect
+from .stroke_circle import StrokeCircle
+from .stroke_circle_segment import StrokeCircleSegment
+from .stroke_path import StrokePath
+from .stroke_round_line import StrokeRoundLine
+from .stroke_square_line import StrokeSquareLine
--- /dev/null
+class Shape:
+ def fit(self, pos, unit):
+ raise NotImplementedError
+
+ def draw(self, surf, color):
+ pass
+
+
+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)
--- /dev/null
+from .base import Shape
+from .ellipse import Ellipse
+
+
+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)
--- /dev/null
+import pygame
+
+from .rect import Rect
+
+
+class Ellipse(Rect):
+ def draw(self, surf, color):
+ pygame.draw.ellipse(
+ surf, color, pygame.Rect(self.left, self.top, self.width, self.height)
+ )
--- /dev/null
+import pygame
+
+from .base import Shape
+
+
+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)
--- /dev/null
+from itertools import chain
+
+import pygame
+
+from .base import Shape
+
+
+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)
+ 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)
+ )
--- /dev/null
+from math import cos, sin, tau
+
+from .stroke_path import StrokePath
+from .stroke_round_line import StrokeRoundLine
+
+
+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),
+ )
--- /dev/null
+from math import cos, sin, tau
+
+
+from .stroke_circle import StrokeCircle
+from .stroke_round_line import StrokeRoundLine
+
+
+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),
+ )
--- /dev/null
+from .base import Shapes
+from .stroke_round_line import StrokeRoundLine
+
+
+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
--- /dev/null
+from math import acos, atan2, ceil, cos, floor, pi, sin, sqrt, tau
+
+from .polygon import Polygon
+
+
+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),
+ )
--- /dev/null
+from math import sqrt
+
+from .polygon import Polygon
+
+
+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),
+ )
+++ /dev/null
-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),
- )
from math import cos, pi, sin, tau
-from vectors.vectors import (
+from vectors import (
Polygon, Rect, Shapes, StrokeCircleSegment, StrokeRoundLine, StrokeSquareLine
)
import pygame
from .bluetooth import BluetoothConf
-from ui.ui import Button, FPSWidget, Icon, IconButton, Root, Switch
+from ui import Button, FPSWidget, Icon, IconButton, Root, Switch
from .shapes import (
bluetooth,
laptop_double,