hexagram/py/hexagram/cluster.py

237 lines
6.5 KiB
Python

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)