438 lines
14 KiB
Python
438 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
import sys
|
|
import math
|
|
from qtpy import QtCore, QtWidgets
|
|
from qtpy.QtCore import Qt, QPoint, QPointF, QLine, QRect, QSize, Slot, Property
|
|
from qtpy.QtGui import QPainter, QBrush, QPen, QFont, QColor, QRadialGradient
|
|
from qtvcp.widgets.widget_baseclass import _HalWidgetBase, hal
|
|
|
|
class Gauge(QtWidgets.QWidget, _HalWidgetBase):
|
|
def __init__(self, parent=None):
|
|
super(Gauge, self).__init__(parent)
|
|
self._threshold = 0
|
|
self._setpoint = QPointF(0, 0)
|
|
self._num_ticks = 11
|
|
self._max_value = 100
|
|
self._max_reading = 100
|
|
self._gauge_label = "GAUGE"
|
|
self._dial_font_size = 10
|
|
self._value_font_size = 10
|
|
self._label_font_size = 10
|
|
self._base_gradient_color = QColor(180, 180, 180)
|
|
self._base_color = QColor(40, 40, 40)
|
|
self._center_gradient_color = QColor("gray")
|
|
self._center_color = QColor("#404040")
|
|
self._zone1_color = QColor("green")
|
|
self._zone2_color = QColor("red")
|
|
self._bezel_color = QColor("gray")
|
|
self._bezel_width = 6
|
|
self.qpa = list()
|
|
self.canvas = QRect()
|
|
self.canvas.setSize(QSize(self.width(), self.height()))
|
|
self.tick_width = 2
|
|
self.arc_width = 10
|
|
self.value = 0
|
|
self._halpin_option = True
|
|
self.create_unit_array()
|
|
|
|
def _hal_init(self):
|
|
if (self._halpin_option):
|
|
self.hal_pin = self.HAL_GCOMP_.newpin(self.HAL_NAME_ + "_value", hal.HAL_FLOAT, hal.HAL_IN)
|
|
self.hal_pin.value_changed.connect(lambda value: self.update_value(value))
|
|
self.hal_pin = self.HAL_GCOMP_.newpin(self.HAL_NAME_ + "_setpoint", hal.HAL_FLOAT, hal.HAL_IN)
|
|
self.hal_pin.value_changed.connect(lambda value: self.set_setpoint(value))
|
|
|
|
def create_unit_array(self):
|
|
self.qpa = list(range(self._num_ticks))
|
|
inc = 270.0 / (self._num_ticks - 1)
|
|
for i in range(self._num_ticks):
|
|
angle = (inc * i) - 225
|
|
x = math.cos(math.radians(angle))
|
|
y = math.sin(math.radians(angle))
|
|
self.qpa[i] = QPointF(x, y)
|
|
|
|
def paintEvent(self, event):
|
|
w = int(min(event.rect().width(), event.rect().height()))
|
|
painter = QPainter(self)
|
|
painter.setRenderHint(painter.Antialiasing)
|
|
self.draw_background(painter, event, w)
|
|
self.draw_zones(painter, event, w)
|
|
self.draw_gauge(painter, event, w)
|
|
self.draw_digits(painter, event, w)
|
|
self.draw_setpoint(painter, event, w)
|
|
self.draw_needle(painter, event, w)
|
|
self.draw_center(painter, event, w)
|
|
self.draw_readout(painter, event, w)
|
|
painter.end()
|
|
|
|
def draw_background(self, qp, event, w):
|
|
w -= 6
|
|
center = event.rect().center()
|
|
rect = QRect()
|
|
rect.setSize(QSize(w, w))
|
|
rect.moveCenter(center)
|
|
fp = QPointF(center.x() - w/4, center.y() - w/4)
|
|
bg = QRadialGradient(QPointF(center), w/2, fp)
|
|
bg.setColorAt(0, self._base_gradient_color)
|
|
bg.setColorAt(1, self._base_color)
|
|
qp.setPen(QPen(self._bezel_color, self._bezel_width))
|
|
qp.setBrush(QBrush(bg))
|
|
qp.drawEllipse(rect)
|
|
qp.drawArc(rect, 0, 360 * 16)
|
|
|
|
def draw_zones(self, qp, event, w):
|
|
segment1 = -45
|
|
span1 = int(((self._max_value - self._threshold) * 270) / self._max_value)
|
|
segment2 = span1 - 45
|
|
span2 = 270 - span1
|
|
rect = QRect()
|
|
w = int(w/2)
|
|
rect.setSize(QSize(w, w))
|
|
rect.moveCenter(event.rect().center())
|
|
qp.setPen(QPen(self._zone1_color, self.arc_width, cap = Qt.FlatCap))
|
|
qp.drawArc(rect, segment1*16, span1*16)
|
|
qp.setPen(QPen(self._zone2_color, self.arc_width, cap = Qt.FlatCap))
|
|
qp.drawArc(rect, segment2*16, span2*16)
|
|
|
|
def draw_gauge(self, qp, event, w):
|
|
w *= 0.6
|
|
w = int(w)
|
|
rect = QRect()
|
|
rect.setSize(QSize(w, w))
|
|
rect.moveCenter(event.rect().center())
|
|
center = QPointF(rect.center())
|
|
qp.setPen(QPen(Qt.white, self.tick_width, cap = Qt.FlatCap))
|
|
qp.drawArc(rect, (-45 * 16), (270 * 16))
|
|
rad = rect.width()/2
|
|
inc = 270.0 / (self._num_ticks - 1)
|
|
for i in range(self._num_ticks):
|
|
p1 = self.qpa[i] * rad
|
|
p1 += center
|
|
p2 = self.qpa[i] * (rad + 10)
|
|
p2 += center
|
|
line = QLine(p1.toPoint(), p2.toPoint())
|
|
qp.drawLine(line)
|
|
|
|
def draw_digits(self, qp, event, w):
|
|
w *= 0.8
|
|
center = event.rect().center()
|
|
rect = QRect()
|
|
rect.setSize(QSize(40,18))
|
|
qp.setPen(QPen(Qt.white, self.tick_width))
|
|
qp.setFont(QFont('Lato Heavy', self._dial_font_size))
|
|
rad = w/2
|
|
inc = 270.0 / (self._num_ticks - 1)
|
|
for i in range(self._num_ticks):
|
|
angle = (inc * i) - 225
|
|
q, r = divmod(self._max_reading * i, self._num_ticks - 1)
|
|
text = str(q) if r == 0 else ""
|
|
if text == "": continue
|
|
x = int(rad * math.cos(math.radians(angle)) + center.x())
|
|
y = int(rad * math.sin(math.radians(angle)) + center.y())
|
|
rect.moveCenter(QPoint(x, y))
|
|
qp.drawText(rect, Qt.AlignCenter, text)
|
|
|
|
def draw_center(self, qp, event, w):
|
|
w *= 0.2
|
|
w = int(w)
|
|
rect = QRect()
|
|
rect.setSize(QSize(w, w))
|
|
rect.moveCenter(event.rect().center())
|
|
rad = rect.width()/2
|
|
cap = QRadialGradient(QPointF(rect.center()), rad)
|
|
cap.setColorAt(0, self._center_gradient_color)
|
|
cap.setColorAt(1, self._center_color)
|
|
qp.setPen(QPen(self._bezel_color, 1.5))
|
|
qp.setBrush(QBrush(cap))
|
|
qp.drawEllipse(rect)
|
|
|
|
def draw_setpoint(self, qp, event, w):
|
|
w *= 0.6
|
|
center = QPointF(event.rect().center())
|
|
rad = w/2
|
|
rect = QRect(0, 0, 8, 8)
|
|
p = self._setpoint * rad
|
|
p += center
|
|
rect.moveCenter(p.toPoint())
|
|
qp.fillRect(rect, QColor(Qt.yellow))
|
|
|
|
def draw_needle(self, qp, event, w):
|
|
w *= 0.6
|
|
center = event.rect().center()
|
|
angle = ((self.value * 270.0) / self._max_value) - 225
|
|
rad = w/2
|
|
x = int(rad * math.cos(math.radians(angle)) + center.x())
|
|
y = int(rad * math.sin(math.radians(angle)) + center.y())
|
|
line = QLine(center.x(), center.y(), x, y)
|
|
qp.setPen(QPen(Qt.red, 4))
|
|
qp.drawLine(line)
|
|
|
|
def draw_readout(self, qp, event, w):
|
|
center = event.rect().center()
|
|
rect = QRect()
|
|
rect.setSize(QSize(int(w/4), int(w/8)))
|
|
rect.moveCenter(QPoint(center.x(), center.y() + int(w/4)))
|
|
text = "{}".format(self.value)
|
|
qp.setPen(QPen(Qt.white, 4))
|
|
qp.setFont(QFont('Lato Heavy', self._value_font_size))
|
|
qp.drawText(rect, Qt.AlignCenter, text)
|
|
rect.moveCenter(QPoint(center.x(), center.y() + int(w/3)))
|
|
text = self._gauge_label
|
|
qp.setFont(QFont('Lato Heavy', self._label_font_size))
|
|
qp.drawText(rect, Qt.AlignCenter, text)
|
|
|
|
@Slot(float)
|
|
@Slot(int)
|
|
def update_value(self, value):
|
|
if value != self.value:
|
|
self.value = int(value)
|
|
self.update()
|
|
|
|
def set_setpoint(self, value):
|
|
if value >= self._max_value:
|
|
angle = 45
|
|
elif value <= 0:
|
|
angle = -225
|
|
else:
|
|
angle = ((value * 270) / self._max_value) - 225
|
|
x = math.cos(math.radians(angle))
|
|
y = math.sin(math.radians(angle))
|
|
self._setpoint = QPointF(x, y)
|
|
self.update()
|
|
|
|
@Slot(int)
|
|
def set_threshold(self, value):
|
|
if value > self._max_value:
|
|
self._threshold = self._max_value
|
|
elif value < 0:
|
|
self._threshold = 0
|
|
else:
|
|
self._threshold = value
|
|
self.update()
|
|
|
|
def get_threshold(self):
|
|
return self._threshold
|
|
|
|
def reset_threshold(self):
|
|
self._threshold = 50
|
|
|
|
@Slot(int)
|
|
def set_max_value(self, value):
|
|
self._max_value = value
|
|
|
|
def get_max_value(self):
|
|
return self._max_value
|
|
|
|
def reset_max_value(self):
|
|
self._max_value = 100
|
|
|
|
@Slot(int)
|
|
def set_max_reading(self, value):
|
|
self._max_reading = value
|
|
self.update()
|
|
|
|
def get_max_reading(self):
|
|
return self._max_reading
|
|
|
|
def reset_max_reading(self):
|
|
self._max_reading = 100
|
|
|
|
@Slot(int)
|
|
def set_num_ticks(self, value):
|
|
# cannot allow value <= 1 or there will be division by 0 errors
|
|
self._num_ticks = value if value > 1 else 2
|
|
self.create_unit_array()
|
|
self.update()
|
|
|
|
def get_num_ticks(self):
|
|
return self._num_ticks
|
|
|
|
def reset_num_ticks(self):
|
|
self._num_ticks = 11
|
|
|
|
@Slot(str)
|
|
def set_label(self, label):
|
|
self._gauge_label = label
|
|
self.update()
|
|
|
|
def get_label(self):
|
|
return self._gauge_label
|
|
|
|
def reset_label(self):
|
|
self._gauge_label = "GAUGE"
|
|
|
|
@Slot(QColor)
|
|
def set_base_gradient_color(self, color):
|
|
self._base_gradient_color = color
|
|
self.update()
|
|
|
|
def get_base_gradient_color(self):
|
|
return self._base_gradient_color
|
|
|
|
def reset_base_gradient_color(self):
|
|
self._base_gradient_color = QColor(180, 180, 180)
|
|
self.update()
|
|
|
|
@Slot(QColor)
|
|
def set_base_color(self, color):
|
|
self._base_color = color
|
|
self.update()
|
|
|
|
def get_base_color(self):
|
|
return self._base_color
|
|
|
|
def reset_base_color(self):
|
|
self._base_color = QColor(40, 40, 40)
|
|
self.update()
|
|
|
|
@Slot(QColor)
|
|
def set_center_gradient_color(self, color):
|
|
self._center_gradient_color = color
|
|
self.update()
|
|
|
|
def get_center_gradient_color(self):
|
|
return self._center_gradient_color
|
|
|
|
def reset_center_gradient_color(self):
|
|
self._center_gradient_color = QColor("gray")
|
|
self.update()
|
|
|
|
@Slot(QColor)
|
|
def set_center_color(self, color):
|
|
self._center_color = color
|
|
self.update()
|
|
|
|
def get_center_color(self):
|
|
return self._center_color
|
|
|
|
def reset_center_color(self):
|
|
self._center_color = QColor("#404040")
|
|
self.update()
|
|
|
|
@Slot(QColor)
|
|
def set_zone2_color(self, color):
|
|
self._zone2_color = color
|
|
self.update()
|
|
|
|
def get_zone2_color(self):
|
|
return self._zone2_color
|
|
|
|
def reset_zone2_color(self):
|
|
self._zone2_color = QColor("red")
|
|
|
|
@Slot(QColor)
|
|
def set_zone1_color(self, color):
|
|
self._zone1_color = color
|
|
self.update()
|
|
|
|
def get_zone1_color(self):
|
|
return self._zone1_color
|
|
|
|
def reset_zone1_color(self):
|
|
self._zone1_color = QColor("green")
|
|
|
|
@Slot(QColor)
|
|
def set_zone2_color(self, color):
|
|
self._zone2_color = color
|
|
self.update()
|
|
|
|
def get_zone2_color(self):
|
|
return self._zone2_color
|
|
|
|
def reset_zone2_color(self):
|
|
self._zone2_color = QColor("red")
|
|
|
|
@Slot(QColor)
|
|
def set_bezel_color(self, color):
|
|
self._bezel_color = color
|
|
self.update()
|
|
|
|
def get_bezel_color(self):
|
|
return self._bezel_color
|
|
|
|
def reset_bezel_color(self):
|
|
self._bezel_color = QColor("gray")
|
|
|
|
def sizeHint(self):
|
|
return QtCore.QSize(200, 200)
|
|
|
|
def set_halpin_option(self, value):
|
|
self._halpin_option = value
|
|
|
|
def get_halpin_option(self):
|
|
return self._halpin_option
|
|
|
|
def reset_halpin_option(self):
|
|
self._halpin_option = True
|
|
|
|
@Slot(int)
|
|
def set_bezel_width(self, value):
|
|
self._bezel_width = value
|
|
self.update()
|
|
|
|
def get_bezel_width(self):
|
|
return self._bezel_width
|
|
|
|
def reset_bezel_width(self):
|
|
self._bezel_width = 6
|
|
self.update()
|
|
|
|
def sizeHint(self):
|
|
return QtCore.QSize(200, 200)
|
|
|
|
def set_halpin_option(self, value):
|
|
self._halpin_option = value
|
|
|
|
def get_halpin_option(self):
|
|
return self._halpin_option
|
|
|
|
def reset_halpin_option(self):
|
|
self._halpin_option = True
|
|
|
|
halpin_option = Property(bool, get_halpin_option, set_halpin_option, reset_halpin_option)
|
|
threshold = Property(int, get_threshold, set_threshold, reset_threshold)
|
|
max_value = Property(int, get_max_value, set_max_value, reset_max_value)
|
|
max_reading = Property(int, get_max_reading, set_max_reading, reset_max_reading)
|
|
num_ticks = Property(int, get_num_ticks, set_num_ticks, reset_num_ticks)
|
|
gauge_label = Property(str, get_label, set_label, reset_label)
|
|
base_gradient_color = Property(QColor, get_base_gradient_color, set_base_gradient_color, reset_base_gradient_color)
|
|
base_color = Property(QColor, get_base_color, set_base_color, reset_base_color)
|
|
center_gradient_color = Property(QColor, get_center_gradient_color, set_center_gradient_color, reset_center_gradient_color)
|
|
center_color = Property(QColor, get_center_color, set_center_color, reset_center_color)
|
|
zone1_color = Property(QColor, get_zone1_color, set_zone1_color, reset_zone1_color)
|
|
zone2_color = Property(QColor, get_zone2_color, set_zone2_color, reset_zone2_color)
|
|
bezel_color = Property(QColor, get_bezel_color, set_bezel_color, reset_bezel_color)
|
|
bezel_width = Property(int, get_bezel_width, set_bezel_width, reset_bezel_width)
|
|
|
|
#############################
|
|
# Testing #
|
|
#############################
|
|
if __name__ == "__main__":
|
|
import sys
|
|
from qtpy.QtWidgets import QSlider, QWidget, QVBoxLayout
|
|
app = QtWidgets.QApplication(sys.argv)
|
|
w = QWidget()
|
|
w.setGeometry(100, 100, 400, 400)
|
|
w.setWindowTitle('Round Gauge')
|
|
layout = QVBoxLayout(w)
|
|
gauge = Gauge(w)
|
|
gauge.set_max_value(20000)
|
|
gauge.set_max_reading(20)
|
|
gauge.set_threshold(7200)
|
|
gauge.set_setpoint(14000)
|
|
gauge.set_num_ticks(11)
|
|
gauge.set_label("RPM")
|
|
gauge._value_font_size = 10
|
|
gauge._label_font_size = 10
|
|
gauge._dial_font_size = 10
|
|
slider = QSlider(Qt.Horizontal)
|
|
slider.setMinimum(0)
|
|
slider.setMaximum(20000)
|
|
slider.setSingleStep(10)
|
|
slider.setPageStep(100)
|
|
slider.valueChanged.connect(gauge.update_value)
|
|
layout.addWidget(gauge)
|
|
layout.addWidget(slider)
|
|
w.show()
|
|
sys.exit( app.exec() )
|