Diagnostics as dialogs (no dock) + real car-style gauges
Diagnostics: removed the always-on right-side Diagnostics dock (it ate screen space on every view). Read Codes now opens a modal dialog listing codes grouped stored/pending/permanent with descriptions (no-start codes bold red); Clear Codes keeps its confirmation, and on re-read pops a dialog only if codes returned. Dropped the View > Show Diagnostics Panel toggle. Gauges: replaced the plain progress-arc with a proper automotive dial -- round face + bezel, major/minor tick marks, a numeric scale (rounded labels), a red needle + center hub, a colored rim arc for the value, and an amber peak tick. Digital readout + name/unit below. Validated headless: no Diagnostics dock; Read Codes opens the Trouble Codes dialog; gauge view renders. obdcore + diagnostics tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
This commit is contained in:
+78
-30
@@ -156,7 +156,7 @@ class ArcGauge(QtWidgets.QWidget):
|
||||
self.accent = QtGui.QColor(accent)
|
||||
self.value = None
|
||||
self.peak = None
|
||||
self.setMinimumSize(150, 130)
|
||||
self.setMinimumSize(172, 176)
|
||||
|
||||
def set_value(self, v, peak=None):
|
||||
self.value = v
|
||||
@@ -166,45 +166,93 @@ class ArcGauge(QtWidgets.QWidget):
|
||||
def _frac(self, v):
|
||||
return max(0.0, min(1.0, (v - self.vmin) / (self.vmax - self.vmin)))
|
||||
|
||||
# gauge geometry: a 270-degree dial, gap at the bottom (like a tach)
|
||||
_START = 225.0 # value=min angle (math degrees, 0=3 o'clock, CCW+)
|
||||
_SWEEP = 270.0 # clockwise as value rises
|
||||
|
||||
def _ang(self, frac):
|
||||
return self._START - self._SWEEP * max(0.0, min(1.0, frac))
|
||||
|
||||
def paintEvent(self, _ev):
|
||||
p = QtGui.QPainter(self)
|
||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
w, h = self.width(), self.height()
|
||||
p.fillRect(self.rect(), QtGui.QColor("#141414")) # own dark bg (theme-proof)
|
||||
m = 14
|
||||
side = min(w, h - 18)
|
||||
rect = QtCore.QRectF((w - side) / 2 + m / 2, m / 2, side - m, side - m)
|
||||
start, span = 225 * 16, -270 * 16 # 270deg sweep, top-open down
|
||||
p.fillRect(self.rect(), QtGui.QColor("#141414"))
|
||||
|
||||
def arc_pen(color, width):
|
||||
pen = QtGui.QPen(QtGui.QColor(color), width)
|
||||
pen.setCapStyle(QtCore.Qt.RoundCap)
|
||||
return pen
|
||||
side = min(w, h - 14)
|
||||
R = side / 2 - 6
|
||||
cx, cy = w / 2.0, R + 8
|
||||
|
||||
p.setPen(arc_pen("#333", 9))
|
||||
p.drawArc(rect, start, span)
|
||||
def pt(ang_deg, r):
|
||||
a = math.radians(ang_deg)
|
||||
return QtCore.QPointF(cx + r * math.cos(a), cy - r * math.sin(a))
|
||||
|
||||
# dial face + bezel
|
||||
p.setPen(QtGui.QPen(QtGui.QColor("#2a2a2a"), 3))
|
||||
p.setBrush(QtGui.QColor("#0c0c0c"))
|
||||
p.drawEllipse(QtCore.QPointF(cx, cy), R, R)
|
||||
|
||||
# value progress arc just inside the rim
|
||||
rimrect = QtCore.QRectF(cx - R + 7, cy - R + 7, 2 * (R - 7), 2 * (R - 7))
|
||||
if self.value is not None:
|
||||
frac = self._frac(self.value)
|
||||
p.setPen(arc_pen(self.accent, 9))
|
||||
p.drawArc(rect, start, int(span * frac))
|
||||
if self.peak is not None and self.peak != self.value:
|
||||
pf = self._frac(self.peak)
|
||||
ang = (225 - 270 * pf)
|
||||
p.setPen(QtGui.QPen(QtGui.QColor("#e6c84b"), 2))
|
||||
cx, cy = rect.center().x(), rect.center().y()
|
||||
r1, r2 = side / 2 - m - 9, side / 2 - m + 2
|
||||
a = math.radians(ang)
|
||||
p.drawLine(QtCore.QPointF(cx + r1 * math.cos(a), cy - r1 * math.sin(a)),
|
||||
QtCore.QPointF(cx + r2 * math.cos(a), cy - r2 * math.sin(a)))
|
||||
pen = QtGui.QPen(QtGui.QColor(self.accent), 4)
|
||||
pen.setCapStyle(QtCore.Qt.FlatCap)
|
||||
p.setPen(pen)
|
||||
p.drawArc(rimrect, int(self._START * 16),
|
||||
int(-self._SWEEP * self._frac(self.value) * 16))
|
||||
|
||||
p.setPen(QtGui.QColor("#eee"))
|
||||
f = p.font(); f.setPointSize(15); f.setBold(True); p.setFont(f)
|
||||
# tick marks + numeric scale
|
||||
majors = 6
|
||||
fnt = p.font(); fnt.setPointSize(7); p.setFont(fnt)
|
||||
for i in range(majors * 5 + 1):
|
||||
frac = i / (majors * 5)
|
||||
a = self._ang(frac)
|
||||
major = (i % 5 == 0)
|
||||
p.setPen(QtGui.QPen(QtGui.QColor("#ddd" if major else "#555"),
|
||||
2 if major else 1))
|
||||
p.drawLine(pt(a, R - 3), pt(a, R - (13 if major else 7)))
|
||||
if major:
|
||||
val = self.vmin + frac * (self.vmax - self.vmin)
|
||||
span = abs(self.vmax - self.vmin)
|
||||
lbl = f"{val:.0f}" if span >= 50 else f"{val:.1f}" if span >= 5 else f"{val:g}"
|
||||
lp = pt(a, R - 25)
|
||||
p.setPen(QtGui.QColor("#9a9a9a"))
|
||||
p.drawText(QtCore.QRectF(lp.x() - 18, lp.y() - 7, 36, 14),
|
||||
QtCore.Qt.AlignCenter, lbl)
|
||||
|
||||
# peak marker (thin amber tick)
|
||||
if self.peak is not None and self.value is not None and self.peak != self.value:
|
||||
a = self._ang(self._frac(self.peak))
|
||||
p.setPen(QtGui.QPen(QtGui.QColor("#e6c84b"), 2))
|
||||
p.drawLine(pt(a, R - 3), pt(a, R - 14))
|
||||
|
||||
# needle
|
||||
if self.value is not None:
|
||||
a = self._ang(self._frac(self.value))
|
||||
tip = pt(a, R - 16)
|
||||
b1 = pt(a + 90, 4.5)
|
||||
b2 = pt(a - 90, 4.5)
|
||||
tail = pt(a + 180, 10)
|
||||
path = QtGui.QPainterPath()
|
||||
path.moveTo(b1); path.lineTo(tip); path.lineTo(b2); path.lineTo(tail)
|
||||
path.closeSubpath()
|
||||
p.setPen(QtCore.Qt.NoPen)
|
||||
p.setBrush(QtGui.QColor("#e6194B"))
|
||||
p.drawPath(path)
|
||||
# hub
|
||||
p.setBrush(QtGui.QColor("#999")); p.setPen(QtCore.Qt.NoPen)
|
||||
p.drawEllipse(QtCore.QPointF(cx, cy), 5, 5)
|
||||
|
||||
# digital readout (lower face)
|
||||
p.setPen(QtGui.QColor("#fff"))
|
||||
fnt.setPointSize(13); fnt.setBold(True); p.setFont(fnt)
|
||||
val = "--" if self.value is None else f"{self.value:g}"
|
||||
p.drawText(rect, QtCore.Qt.AlignCenter, val)
|
||||
f.setPointSize(8); f.setBold(False); p.setFont(f)
|
||||
p.setPen(QtGui.QColor("#999"))
|
||||
p.drawText(QtCore.QRectF(0, h - 16, w, 14), QtCore.Qt.AlignCenter,
|
||||
p.drawText(QtCore.QRectF(cx - 55, cy + R * 0.30, 110, 22),
|
||||
QtCore.Qt.AlignCenter, val)
|
||||
# name + unit
|
||||
fnt.setPointSize(8); fnt.setBold(False); p.setFont(fnt)
|
||||
p.setPen(QtGui.QColor("#9a9a9a"))
|
||||
p.drawText(QtCore.QRectF(0, h - 15, w, 14), QtCore.Qt.AlignCenter,
|
||||
f"{self.name} ({self.unit})" if self.unit else self.name)
|
||||
p.end()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user