import math import cairo from typing import Iterable, List from hexagram.pattern import HexagonPattern from hexagram.gauge import Gauge from hexagram.dial import Dial from hexagram.speedo import Speedo from hexagram.tacho import Tacho from hexagram.fuel import FuelGauge from hexagram.thermo import ThermoGauge class ShiftIndicator(Gauge): __slots__ = 'x', 'y', 'rpm_min', 'rpm_redline', 'rpm_max', 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,), ) def __init__(self, x: float, y: float, rpm_min: float, rpm_redline: float, rpm_max: float): self.value = 0 self.x = x self.y = y self.rpm_min = rpm_min self.rpm_redline = rpm_redline self.rpm_max = rpm_max def draw_level(self, cr: cairo.Context, level: int, on: bool=False): r = self.LIGHT_COLORS[level][0] g = self.LIGHT_COLORS[level][1] b = self.LIGHT_COLORS[level][2] if not on: r *= self.LIGHT_LOW g *= self.LIGHT_LOW b *= self.LIGHT_LOW 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() def draw_bg(self, cr: cairo.Context): for level in range(0, len(self.LIGHT_LEVELS)): self.draw_level(cr, level) def draw_fg(self, cr: cairo.Context): if self.value < self.rpm_min: return level = math.ceil((self.value - self.rpm_min) / self.rpm_redline) for l in range(0, len(self.LIGHT_LEVELS)): self.draw_level(cr, level, l >= level) class Odometer(Gauge): __slots__ = 'x', 'y', def __init__(self, x: float, y: float): self.value = 0 self.x = x self.y = y def draw_bg(self, cr: cairo.Context): pass def draw_fg(self, cr: cairo.Context): cr.set_source_rgb(1, 1, 1) cr.select_font_face("Muli", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD) cr.set_font_size(24) cr.move_to(self.x, self.y) cr.show_text("%d mi" % self.value) class AmbientTemp(Gauge): __slots__ = 'x', 'y', def __init__(self, x: float, y: float): self.value = 0 self.x = x self.y = y def draw_bg(self, cr: cairo.Context): pass def draw_fg(self, cr: cairo.Context): cr.set_source_rgb(1, 1, 1) cr.select_font_face("Muli", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD) cr.set_font_size(24) cr.move_to(self.x, self.y) cr.show_text("%.1f°C" % self.value) class Clock(Gauge): __slots__ = 'x', 'y', def __init__(self, x: float, y: float): self.value = 0 self.x = x self.y = y def draw_bg(self, cr: cairo.Context): pass def draw_fg(self, cr: cairo.Context): cr.set_source_rgb(1, 1, 1) cr.select_font_face("Muli", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD) cr.set_font_size(24) cr.move_to(self.x, self.y) cr.show_text("%d:%02d" % ( (self.value - (self.value % 60)) / 60, self.value % 60 )) class Cluster(): __slots__ = 'gauges', 'speedo', 'fuel', 'tacho', 'thermo', 'odo', \ 'ambient', 'clock' WIDTH = 1280 HEIGHT = 480 SHIFT_INDICATOR_X = 392 SHIFT_INDICATOR_Y = 24 def __init__(self, rpm_min: float, rpm_redline: float, rpm_max: float): 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 = ThermoGauge(self.WIDTH - self.HEIGHT / 2, self.HEIGHT / 2, self.HEIGHT / 2) self.odo = Odometer(0.8 * self.HEIGHT, self.HEIGHT - 16) self.ambient = AmbientTemp(self.WIDTH - self.HEIGHT, self.HEIGHT - 16) self.clock = Clock(self.WIDTH / 2 - 32, self.HEIGHT - 16) 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(ShiftIndicator(self.SHIFT_INDICATOR_X, self.SHIFT_INDICATOR_Y, rpm_min, rpm_redline, rpm_max)) def draw_bg(self, cr: cairo.Context): cr.set_source_rgb(0.1, 0.1, 0.1) cr.rectangle(0, 0, self.WIDTH, self.HEIGHT) cr.fill() cr.rectangle(0, 0, self.WIDTH, self.HEIGHT) pattern = HexagonPattern() pattern.fill(cr) # Top dark area cr.set_source_rgba(0, 0, 0, 0.5) cr.rectangle(0, 0, self.WIDTH, 64) cr.fill() cr.set_source_rgba(1, 1, 1, 0.25) cr.move_to(0, 64) cr.line_to(self.WIDTH, 64) cr.stroke() # Bottom dark area cr.set_source_rgba(0, 0, 0, 0.5) cr.rectangle(0, self.HEIGHT - 52, self.WIDTH, 52) cr.fill() cr.set_source_rgba(1, 1, 1, 0.25) cr.move_to(0, self.HEIGHT - 52) cr.line_to(self.WIDTH, self.HEIGHT - 52) cr.stroke() for gauge in self.gauges: gauge.draw_bg(cr) def draw_fg(self, cr: cairo.Context): for gauge in self.gauges: fn = getattr(gauge, 'draw_fg', None) if fn is not None: fn(cr)