244 lines
6.7 KiB
Python
244 lines
6.7 KiB
Python
import math
|
|
import cairo
|
|
|
|
from typing import Iterable
|
|
|
|
class Path():
|
|
__slots__ = 'commands',
|
|
|
|
def __init__(self, commands: Iterable):
|
|
self.commands = commands
|
|
|
|
def horiz_to(self, cr: cairo.Context, x2: float):
|
|
_, y = cr.get_current_point()
|
|
|
|
return cr.line_to(x2, y)
|
|
|
|
def rel_horiz_to(self, cr: cairo.Context, x2: float):
|
|
x, y = cr.get_current_point()
|
|
|
|
return cr.line_to(x + x2, y)
|
|
|
|
def vert_to(self, cr: cairo.Context, y2: float):
|
|
x, _ = cr.get_current_point()
|
|
|
|
return cr.line_to(x, y2)
|
|
|
|
def rel_vert_to(self, cr: cairo.Context, y2: float):
|
|
x, y = cr.get_current_point()
|
|
|
|
return cr.line_to(x, y + y2)
|
|
|
|
def draw(self, cr: cairo.Context):
|
|
last = None
|
|
|
|
for item in self.commands:
|
|
command, args = item
|
|
|
|
if command == 'M':
|
|
for arg in args:
|
|
cr.move_to(*arg)
|
|
elif command == 'm':
|
|
for arg in args:
|
|
if last == 'Z' or last == 'z':
|
|
cr.move_to(*arg)
|
|
else:
|
|
cr.rel_move_to(*arg)
|
|
elif command == 'L':
|
|
for arg in args:
|
|
cr.line_to(*arg)
|
|
elif command == 'l':
|
|
for arg in args:
|
|
cr.rel_line_to(*arg)
|
|
elif command == 'H':
|
|
for arg in args:
|
|
self.horiz_to(cr, arg)
|
|
elif command == 'h':
|
|
for arg in args:
|
|
self.rel_horiz_to(cr, arg)
|
|
elif command == 'V':
|
|
for arg in args:
|
|
self.vert_to(cr, arg)
|
|
elif command == 'v':
|
|
for arg in args:
|
|
self.rel_vert_to(cr, arg)
|
|
elif command == 'C':
|
|
for arg in args:
|
|
cr.curve_to(*arg)
|
|
elif command == 'c':
|
|
for arg in args:
|
|
cr.rel_curve_to(*arg)
|
|
elif command == 'Z' or command == 'z':
|
|
cr.close_path()
|
|
|
|
last = command
|
|
|
|
class HexagonPattern(Path):
|
|
COMMANDS = [
|
|
['M', [[23.246093, 0],
|
|
[0, 13.382812]]],
|
|
['V', [44.689453]],
|
|
['L', [[0.0839844, 44.640625],
|
|
[24.033203, 58.505859],
|
|
[24.001953, 86.332031],
|
|
[22.841796, 87]]],
|
|
['h', [4.478516]],
|
|
['L', [[26.068359, 86.275391],
|
|
[26.099609, 58.449219],
|
|
[50.083984, 44.640625],
|
|
[74.033203, 58.505859],
|
|
[74.001953, 86.332031],
|
|
[72.841796, 87]]],
|
|
['h', [4.478516]],
|
|
['L', [[ 76.068359, 86.275391],
|
|
[ 76.099609, 58.449219],
|
|
[100, 44.689453]]],
|
|
['V', [13.365234]],
|
|
['L', [[76.919921, 0]]],
|
|
['H', [73.246093]],
|
|
['L', [[50.015625, 13.373047],
|
|
[26.919921, 0]]],
|
|
['Z', []],
|
|
['M', [[25.083984, 1.25],
|
|
[49.033203, 15.115234],
|
|
[49.001953, 42.941406],
|
|
[25.017578, 56.75],
|
|
[1.0019531, 42.845703]]],
|
|
['l', [[0.033203, -27.75]]],
|
|
['z', []],
|
|
['m', [[50, 0],
|
|
[24.017576, 13.904297],
|
|
[-0.0352, 27.75]]],
|
|
['L', [[75.017578, 56.75],
|
|
[51.068359, 42.884766],
|
|
[51.099609, 15.058594]]],
|
|
['Z', []]
|
|
]
|
|
|
|
def __init__(self):
|
|
super().__init__(self.COMMANDS)
|
|
|
|
class Gauge():
|
|
pass
|
|
|
|
class Dial(Gauge):
|
|
__slots = 'x', 'y', 'radius',
|
|
|
|
BEZEL_WIDTH = 16
|
|
|
|
BEZEL_GRADIENT_STOPS = (
|
|
(0, 1.0, 0.4, 1.0, 1.0),
|
|
(1, 0.4, 0.0, 0.4, 0.0)
|
|
)
|
|
|
|
def __init__(self, x: float, y: float, radius: float):
|
|
self.x = x
|
|
self.y = y
|
|
self.radius = radius
|
|
|
|
def _gradient(self):
|
|
gradient = cairo.LinearGradient(self.x - self.radius,
|
|
self.y - self.radius,
|
|
self.x + self.radius,
|
|
self.y + self.radius)
|
|
|
|
for stop in self.BEZEL_GRADIENT_STOPS:
|
|
gradient.add_color_stop_rgba(*stop)
|
|
|
|
return gradient
|
|
|
|
def draw_bg(self, cr: cairo.Context):
|
|
arc = (self.x,
|
|
self.y,
|
|
self.radius - self.BEZEL_WIDTH,
|
|
0,
|
|
2.0 * math.pi)
|
|
|
|
# Gauge face
|
|
cr.set_source_rgba(0, 0, 0, 1)
|
|
cr.arc(*arc)
|
|
cr.fill()
|
|
|
|
# Gauge bezel
|
|
cr.set_source(self._gradient())
|
|
cr.set_line_width(self.BEZEL_WIDTH)
|
|
cr.arc(*arc)
|
|
cr.stroke()
|
|
|
|
class ShiftIndicator(Gauge):
|
|
LIGHT_WIDTH = 48
|
|
LIGHT_HEIGHT = 12
|
|
LIGHT_SPACING = 8
|
|
LIGHT_LOW = 0.25
|
|
|
|
LIGHT_COLORS = (
|
|
(0.0, 1.0, 0.0),
|
|
(0.6, 1.0, 0.0),
|
|
(1.0, 1.0, 0.0),
|
|
(1.0, 0.6, 0.0),
|
|
(1.0, 0.0, 0.0),
|
|
)
|
|
|
|
LIGHT_LEVELS = (
|
|
(0, 8),
|
|
(1, 7),
|
|
(2, 6),
|
|
(3, 5),
|
|
(4,),
|
|
)
|
|
|
|
__slots__ = 'x', 'y', 'rpm_redline', 'rpm_max',
|
|
|
|
def __init__(self, x: float, y: float, rpm_redline: float, rpm_max: float):
|
|
self.x = x
|
|
self.y = y
|
|
self.rpm_redline = rpm_redline
|
|
self.rpm_max = rpm_max
|
|
|
|
def draw_bg(self, cr: cairo.Context):
|
|
for level in range(0, len(self.LIGHT_LEVELS)):
|
|
r = self.LIGHT_LOW * self.LIGHT_COLORS[level][0]
|
|
g = self.LIGHT_LOW * self.LIGHT_COLORS[level][1]
|
|
b = self.LIGHT_LOW * self.LIGHT_COLORS[level][2]
|
|
|
|
cr.set_source_rgb(r, g, b)
|
|
|
|
for i in self.LIGHT_LEVELS[level]:
|
|
x = i * (self.LIGHT_WIDTH + self.LIGHT_SPACING)
|
|
|
|
cr.rectangle(self.x + x,
|
|
self.y,
|
|
self.LIGHT_WIDTH,
|
|
self.LIGHT_HEIGHT)
|
|
|
|
cr.fill()
|
|
|
|
class Cluster():
|
|
WIDTH = 1280
|
|
HEIGHT = 480
|
|
|
|
SHIFT_INDICATOR_X = 392
|
|
SHIFT_INDICATOR_Y = 24
|
|
|
|
__slots__ = 'gauges',
|
|
|
|
def __init__(self, rpm_redline: float, rpm_max: float):
|
|
self.gauges = list()
|
|
|
|
for x in (self.HEIGHT / 2, self.WIDTH - self.HEIGHT / 2):
|
|
self.gauges.append(Dial(x, self.HEIGHT / 2, self.HEIGHT / 2))
|
|
|
|
self.gauges.append(ShiftIndicator(self.SHIFT_INDICATOR_X,
|
|
self.SHIFT_INDICATOR_Y,
|
|
rpm_redline,
|
|
rpm_max))
|
|
|
|
def draw_bg(self, cr: cairo.Context):
|
|
cr.set_source_rgb(0, 0, 0)
|
|
cr.rectangle(0, 0, self.WIDTH, self.HEIGHT)
|
|
cr.fill()
|
|
|
|
|
|
|
|
for gauge in self.gauges:
|
|
gauge.draw_bg(cr)
|