198 lines
6.4 KiB
Python
198 lines
6.4 KiB
Python
import math
|
|
import cairo
|
|
|
|
from typing import Iterable, List
|
|
|
|
from hexagram.box import Align
|
|
from hexagram.pattern import HexagonPattern
|
|
from hexagram.gauge import Gauge, TextGauge
|
|
from hexagram.dial import Dial
|
|
from hexagram.speedo import Speedo
|
|
from hexagram.tacho import Tacho, ShiftIndicator
|
|
from hexagram.fuel import FuelGauge
|
|
from hexagram.thermo import Thermometer
|
|
from hexagram.status import StatusIconBox, ColdStatus, ColdIcon
|
|
|
|
class Odometer(TextGauge):
|
|
def __init__(self, x: float, y: float, align: Align):
|
|
super().__init__(x, y, 0, 999999999, align)
|
|
|
|
def format_text(self):
|
|
return "%.1f mi" % self.value
|
|
|
|
class AmbientTemp(TextGauge):
|
|
__slots__ = 'icon',
|
|
|
|
ICON_WIDTH = 40
|
|
ICON_HEIGHT = 40
|
|
ICON_PADDING = 16
|
|
|
|
COLD_THRESHOLD = 4.0 # °C
|
|
|
|
def __init__(self, x: float, y: float, align: Align):
|
|
super().__init__(x, y, -100, 100, align)
|
|
|
|
self.value = 0
|
|
self.icon = ColdIcon(self.ICON_WIDTH, self.ICON_HEIGHT)
|
|
|
|
def format_text(self):
|
|
return "%.1f°C" % self.value
|
|
|
|
def draw_fg(self, cr: cairo.Context):
|
|
cr.select_font_face(self.FONT_FACE,
|
|
self.FONT_SLANT,
|
|
self.FONT_WEIGHT)
|
|
|
|
text = self.format_text()
|
|
|
|
cr.set_font_size(self.FONT_SIZE)
|
|
|
|
extents = cr.text_extents(text)
|
|
width = extents[2] - extents[0]
|
|
|
|
if self.value <= self.COLD_THRESHOLD:
|
|
width += self.icon.width + self.ICON_PADDING
|
|
|
|
if self.align is Align.LEFT:
|
|
text_x_offset = 0.0
|
|
elif self.align is Align.MIDDLE:
|
|
text_x_offset = self.x - width / 2.0
|
|
elif self.align is Align.RIGHT:
|
|
text_x_offset = self.x - width
|
|
|
|
cr.set_source_rgb(1, 1, 1)
|
|
cr.move_to(text_x_offset, self.y)
|
|
cr.show_text(text)
|
|
|
|
if self.value <= self.COLD_THRESHOLD:
|
|
icon_y_offset = abs(self.icon.height - self.FONT_SIZE) * 2
|
|
|
|
self.icon.draw(cr,
|
|
text_x_offset + width - self.icon.width,
|
|
self.y - icon_y_offset,
|
|
ColdStatus.ON)
|
|
|
|
class Clock(TextGauge):
|
|
def __init__(self, x: float, y: float, align: Align):
|
|
super().__init__(x, y, 0, 9999, align)
|
|
|
|
def format_text(self):
|
|
return "%d:%02d" % (
|
|
(self.value - (self.value % 60)) / 60,
|
|
self.value % 60
|
|
)
|
|
|
|
class Cluster():
|
|
__slots__ = '_redraw', 'bg', 'gauges', 'speedo', 'fuel', 'tacho', 'thermo', \
|
|
'odo', 'ambient', 'clock', 'shift_indicator', 'status',
|
|
|
|
WIDTH = 1280
|
|
HEIGHT = 480
|
|
|
|
SHIFT_INDICATOR_X = 392
|
|
SHIFT_INDICATOR_Y = 24
|
|
|
|
SECTION_HEIGHT_TOP = 64
|
|
SECTION_HEIGHT_BOTTOM = 52
|
|
|
|
def __init__(self, rpm_min: float, rpm_redline: float, rpm_max: float):
|
|
self._redraw = True
|
|
self.bg = cairo.ImageSurface(cairo.FORMAT_ARGB32,
|
|
self.WIDTH,
|
|
self.HEIGHT)
|
|
|
|
self.gauges: List[Gauge] = list()
|
|
|
|
self.speedo = Speedo(self.HEIGHT / 2,
|
|
self.HEIGHT / 2,
|
|
self.HEIGHT / 2,
|
|
200.0)
|
|
|
|
self.fuel = FuelGauge(self.HEIGHT / 2,
|
|
self.HEIGHT / 2,
|
|
self.HEIGHT / 2)
|
|
|
|
self.tacho = Tacho(self.WIDTH - self.HEIGHT / 2,
|
|
self.HEIGHT / 2,
|
|
self.HEIGHT / 2,
|
|
8000)
|
|
|
|
self.thermo = Thermometer(self.WIDTH - self.HEIGHT / 2,
|
|
self.HEIGHT / 2,
|
|
self.HEIGHT / 2)
|
|
|
|
self.odo = Odometer(0.8 * self.HEIGHT, self.HEIGHT - 16, Align.LEFT)
|
|
self.clock = Clock(self.WIDTH / 2, self.HEIGHT - 16, Align.MIDDLE)
|
|
self.ambient = AmbientTemp(self.WIDTH - 0.8 * self.HEIGHT, self.HEIGHT - 16, Align.RIGHT)
|
|
|
|
self.shift_indicator = ShiftIndicator(self.SHIFT_INDICATOR_X,
|
|
self.SHIFT_INDICATOR_Y,
|
|
rpm_min,
|
|
rpm_redline,
|
|
rpm_max)
|
|
|
|
self.status = StatusIconBox(self.WIDTH/ 2 - StatusIconBox.ICON_WIDTH * 3.5,
|
|
self.HEIGHT - self.SECTION_HEIGHT_BOTTOM - 16 - StatusIconBox.ICON_HEIGHT * 3,
|
|
StatusIconBox.ICON_WIDTH * 7,
|
|
StatusIconBox.ICON_HEIGHT * 3)
|
|
|
|
self.gauges.append(self.speedo)
|
|
self.gauges.append(self.fuel)
|
|
self.gauges.append(self.tacho)
|
|
self.gauges.append(self.thermo)
|
|
self.gauges.append(self.odo)
|
|
self.gauges.append(self.ambient)
|
|
self.gauges.append(self.clock)
|
|
self.gauges.append(self.shift_indicator)
|
|
self.gauges.append(self.status)
|
|
|
|
def _update_bg(self):
|
|
cr = cairo.Context(self.bg)
|
|
cr.set_source_rgb(0.1, 0.1, 0.1)
|
|
cr.rectangle(0, 0, self.WIDTH, self.HEIGHT)
|
|
cr.fill()
|
|
|
|
pattern = HexagonPattern()
|
|
cr.rectangle(0, 0, self.WIDTH, self.HEIGHT)
|
|
pattern.fill(cr)
|
|
|
|
# Top dark area
|
|
cr.set_source_rgba(0, 0, 0, 0.5)
|
|
cr.rectangle(0, 0, self.WIDTH, self.SECTION_HEIGHT_TOP)
|
|
cr.fill()
|
|
|
|
cr.set_source_rgba(1, 1, 1, 0.25)
|
|
cr.move_to(0, self.SECTION_HEIGHT_TOP)
|
|
cr.line_to(self.WIDTH, self.SECTION_HEIGHT_TOP)
|
|
cr.stroke()
|
|
|
|
# Bottom dark area
|
|
cr.set_source_rgba(0, 0, 0, 0.5)
|
|
cr.rectangle(0, self.HEIGHT - self.SECTION_HEIGHT_BOTTOM,
|
|
self.WIDTH,
|
|
self.SECTION_HEIGHT_BOTTOM)
|
|
cr.fill()
|
|
|
|
cr.set_source_rgba(1, 1, 1, 0.25)
|
|
cr.move_to(0, self.HEIGHT - self.SECTION_HEIGHT_BOTTOM)
|
|
cr.line_to(self.WIDTH, self.HEIGHT - self.SECTION_HEIGHT_BOTTOM)
|
|
cr.stroke()
|
|
|
|
for gauge in self.gauges:
|
|
gauge.draw_bg(cr)
|
|
|
|
def draw_bg(self, cr: cairo.Context):
|
|
if self._redraw:
|
|
self._update_bg()
|
|
self._redraw = False
|
|
|
|
op = cr.get_operator()
|
|
cr.set_source_surface(self.bg, 0, 0)
|
|
cr.set_operator(cairo.OPERATOR_SOURCE)
|
|
cr.rectangle(0, 0, self.WIDTH, self.HEIGHT)
|
|
cr.fill()
|
|
cr.set_operator(op)
|
|
|
|
def draw_fg(self, cr: cairo.Context):
|
|
for gauge in self.gauges:
|
|
gauge.draw_fg(cr)
|