diff --git a/src/simudator/gui/module_graphics_item/module_widget.py b/src/simudator/gui/module_graphics_item/module_widget.py new file mode 100644 index 0000000000000000000000000000000000000000..b9cc6d81384e902cd7ed68cf9ee5f817530dcafe --- /dev/null +++ b/src/simudator/gui/module_graphics_item/module_widget.py @@ -0,0 +1,219 @@ +from typing import Any +from qtpy.QtCore import Property, QPointF, QRectF, Qt, Slot +from qtpy.QtGui import QBrush, QColor, QFont, QFontMetrics, QPainter, QPainterPath, QPen +from qtpy.QtCore import Signal as pyqtSignal +from qtpy.QtCore import Slot +from qtpy.QtWidgets import ( + QGraphicsItem, + QGraphicsWidget, + QStyleOptionGraphicsItem, + QWidget, + QAction, +) + +from simudator.core.module import Module + +class ModuleWidget(QGraphicsWidget): + DEFAULT_WIDTH = 50*3 + DEFAULT_HEIGHT = 50*3 + + update = pyqtSignal() + + def __init__(self, module: Module, state_vars: list[str], actions: list[QAction], formatters: dict[str, Any], parent: QGraphicsItem | None = None, flags: Qt.WindowFlags | Qt.WindowType = Qt.WindowFlags()) -> None: + super().__init__(parent, flags) + super().__init__(parent, flags) + self.setFlag(QGraphicsItem.ItemIsMovable) + + self._module = module + self._state_vars = state_vars + self._actions = actions + self._formatters = formatters + + # Default values for properties for appearance + # TODO: Put these values in constants? + self._outline_width = 1 + self._outline = self.palette().windowText() + self._background = self.palette().window() + self._text_color = self.palette().windowText().color() + self._text_font = QFont("Sans Serif", 9) + self._padding = 5 + + width = self.DEFAULT_WIDTH + height = self.DEFAULT_HEIGHT + if len(self._state_vars) <= 1: + height = QFontMetrics(self._text_font).height() + 2*self._padding + else: + height = (self._padding + QFontMetrics(self._text_font).height()) * (len(state_vars) + 1) + self._padding + self.resize(width, height) + + def paint( + self, + painter: QPainter | None = None, + option: QStyleOptionGraphicsItem | None = None, + widget: QWidget | None = None, + ) -> None: + painter.save() + + width = self.size().width() + height = self.size().height() + + # Draw the base shape + pen = QPen(self.outline, self.outline_width) + painter.setPen(pen) + painter.setBrush(self.background) + painter.drawPath(self.shape()) + + # Set text specific painter settings + painter.setPen(QPen(self.text_color)) + painter.setBrush(QBrush(self.text_color)) + painter.setFont(self.text_font) + text_flags = Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextWordWrap + + text_rect = self.shape().boundingRect() + + # Draw only name of the module if no state variables are to be shown + if len(self._state_vars) == 0: + painter.drawText(text_rect, text_flags, self._module.get_state()["name"]) + + # Special case: only 1 state variable to show + elif len(self._state_vars) == 1: + value = self._module.get_state()[self._state_vars[0]] + painter.drawText(text_rect, text_flags, f"{self._module.get_state()["name"]}: {value}") + + # Draw the name of the module and each state variable with its value on one row each + else: + text_rect = QRectF(0, 0, width, height/(len(self._state_vars)+1)) + painter.drawText(text_rect, text_flags, self._module.get_state()["name"]) + + for i, state_var in enumerate(self._state_vars): + y_start = height / (len(self._state_vars)+1) * (i+1) + y_end = height / (len(self._state_vars) + 1 ) + text_rect = QRectF(0, y_start, width, y_end) + value = self._module.get_state()[state_var] + painter.drawText(text_rect, text_flags, f"{state_var}: {value}") + + painter.restore() + + @Slot() + def update(self, rect: QRectF | None = None): + # This "override" is needed in order to decorate update() as a + # pyqt slot + super().update() + + def shape(self) -> QPainterPath: + path = QPainterPath() + path.addRect(0, 0, self.size().width(), self.size().height()) + return path + + def boundingRect(self) -> QRectF: + width = self.size().width() + height = self.size().height() + margin = self.outline_width / 2 + return QRectF(0 - margin, 0 - margin, width + margin, height + margin) + + def outline_width(self) -> float: + """Return the outline pen width. + + Returns + ------- + float + Outline pen width. + """ + return self._outline_width + + def set_outline_width(self, width: float) -> None: + """Set the outline pen width. + + Parameters + ---------- + width : float + Outline pen width. + + """ + self._outline_width = width + + def outline(self) -> QBrush: + """Return the outline brush used to create the outline pen. + + Returns + ------- + QBrush + Outline brush. + """ + return self._outline + + def set_outline(self, brush: QBrush) -> None: + """Set the outline brush used to create the outline pen. + + Parameters + ---------- + brush : QBrush + Outline brush. + """ + self._outline = brush + + def background(self) -> QBrush: + """Return the bursh used for filling the background. + + Returns + ------- + QBrush + Background brush. + """ + return self._background + + def set_background(self, brush: QBrush) -> None: + """Set the bursh used for filling the background. + + Parameters + ---------- + brush : QBrush + Background brush. + """ + self._background = brush + + def text_color(self) -> QColor: + """Return the color used for text. + + Returns + ------- + QColor + Text color. + """ + return self._text_color + + def set_text_color(self, color: QColor) -> None: + """Set the color used for text. + + Parameters + ---------- + color : QColor + Text color. + """ + self._text_color = color + + def text_font(self) -> QFont: + """Return the font used for text. + + Returns + ------- + QFont + Text font. + """ + return self._text_font + + def set_text_font(self, font: QFont) -> None: + """Set the font used for text. + + Parameters + ---------- + font : QFont + Text font. + """ + self._text_font = font + + outline_width = Property(float, outline_width, set_outline_width) + outline = Property(QBrush, outline, set_outline) + background = Property(QBrush, background, set_background) + text_color = Property(QColor, text_color, set_text_color) + text_font = Property(QFont, text_font, set_text_font) \ No newline at end of file