Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into lint
Browse files Browse the repository at this point in the history
  • Loading branch information
InvincibleRMC committed Feb 10, 2024
2 parents 7499ab3 + f6da050 commit ff64521
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 66 deletions.
2 changes: 1 addition & 1 deletion src/surface/gui/gui/pilot_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self) -> None:
layout = QHBoxLayout()
self.setLayout(layout)

# Look into QStackedLayout for possibly switching between
# TODO Look into QStackedLayout for possibly switching between
# 1 big camera feed and 2 smaller ones
video_area = SwitchableVideoWidget(
[
Expand Down
44 changes: 34 additions & 10 deletions src/surface/gui/gui/styles/custom_styles.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,45 @@
class Style:
"""Represents a single class that can be applied to gui objects to change their appearance."""
from PyQt6.QtWidgets import QWidget, QPushButton

PROPERTY_NAME: str

class IndicatorMixin(QWidget):

class WidgetState(Style):
"""Represents the state of a widget that can be alternately active or inactive."""

PROPERTY_NAME = "widgetState"
_PROPERTY_NAME = "widgetState"

# A component is running, enabled, or armed
ON = "on"
_ON = "on"

# A component is disabled, not running, or disarmed, but could be enabled through this widget
OFF = "off"
_OFF = "off"

# A component is disabled, not expected to have any effect or perform its function because of
# some external factor, either another widget or something external to the gui
# For example, a the arm button when the pi is not connected
INACTIVE = "inactive"
_INACTIVE = "inactive"

# Removes any state
_NO_STATE = ""

def set_on(self) -> None:
self.setProperty(IndicatorMixin._PROPERTY_NAME, IndicatorMixin._ON)
self._update_style()

def set_off(self) -> None:
self.setProperty(IndicatorMixin._PROPERTY_NAME, IndicatorMixin._OFF)
self._update_style()

def set_inactive(self) -> None:
self.setProperty(IndicatorMixin._PROPERTY_NAME, IndicatorMixin._INACTIVE)
self._update_style()

def remove_state(self) -> None:
self.setProperty(IndicatorMixin._PROPERTY_NAME, IndicatorMixin._NO_STATE)
self._update_style()

def _update_style(self) -> None:
style = self.style()
if style is not None:
style.polish(self)


class ButtonIndicator(QPushButton, IndicatorMixin):
pass
59 changes: 12 additions & 47 deletions src/surface/gui/gui/widgets/arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
GUIEventSubscriber,
)
from gui.styles.custom_styles import (
WidgetState,
ButtonIndicator
)
from mavros_msgs.srv import CommandBool
from PyQt6.QtCore import (
Expand All @@ -14,7 +14,6 @@
)
from PyQt6.QtWidgets import (
QHBoxLayout,
QPushButton,
QWidget,
)

Expand All @@ -30,7 +29,7 @@ class Arm(QWidget):
BUTTON_HEIGHT = 60
BUTTON_STYLESHEET = "QPushButton { font-size: 20px; }"

command_response_signal: pyqtSignal = pyqtSignal(CommandBool.Response)
command_response_signal = pyqtSignal(CommandBool.Response)
vehicle_state_signal = pyqtSignal(VehicleState)

def __init__(self) -> None:
Expand All @@ -39,8 +38,8 @@ def __init__(self) -> None:
layout = QHBoxLayout()
self.setLayout(layout)

self.arm_button = QPushButton()
self.disarm_button = QPushButton()
self.arm_button = ButtonIndicator()
self.disarm_button = ButtonIndicator()

self.arm_button.setText("Arm")
self.disarm_button.setText("Disarm")
Expand All @@ -53,14 +52,8 @@ def __init__(self) -> None:

self.arm_button.setStyleSheet(self.BUTTON_STYLESHEET)
self.disarm_button.setStyleSheet(self.BUTTON_STYLESHEET)
self.arm_button.setProperty(
WidgetState.PROPERTY_NAME,
WidgetState.INACTIVE,
)
self.disarm_button.setProperty(
WidgetState.PROPERTY_NAME,
WidgetState.INACTIVE,
)
self.arm_button.set_inactive()
self.disarm_button.set_inactive()

self.arm_button.clicked.connect(self.arm_clicked)
self.disarm_button.clicked.connect(self.disarm_clicked)
Expand Down Expand Up @@ -100,39 +93,11 @@ def arm_status(self, res: CommandBool.Response) -> None:
def vehicle_state_callback(self, msg: VehicleState) -> None:
if msg.pixhawk_connected:
if msg.armed:
self.arm_button.setProperty(
WidgetState.PROPERTY_NAME,
WidgetState.ON,
)
self.disarm_button.setProperty(
WidgetState.PROPERTY_NAME,
"",
)
self.arm_button.set_on()
self.disarm_button.remove_state()
else:
self.arm_button.setProperty(
WidgetState.PROPERTY_NAME,
"",
)
self.disarm_button.setProperty(
WidgetState.PROPERTY_NAME,
WidgetState.OFF,
)
self.arm_button.remove_state()
self.disarm_button.set_off()
else:
self.arm_button.setProperty(
WidgetState.PROPERTY_NAME,
WidgetState.INACTIVE,
)
self.disarm_button.setProperty(
WidgetState.PROPERTY_NAME,
WidgetState.INACTIVE,
)

for button in (
self.arm_button,
self.disarm_button,
):
style = button.style()
if style is not None:
style.polish(button)
else:
self.arm_client.get_logger().error("Gui button missing a style")
self.arm_button.set_inactive()
self.disarm_button.set_inactive()
31 changes: 31 additions & 0 deletions src/surface/gui/gui/widgets/circle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Optional
from gui.styles.custom_styles import IndicatorMixin
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import QColor
from PyQt6.QtWidgets import QWidget, QLabel


class Circle(QLabel):
def __init__(self, parent: Optional[QWidget] = None,
radius: int = 50,
color: Optional[QColor | Qt.GlobalColor] = None) -> None:
super().__init__(parent)
self.setFixedSize(QSize(2 * radius, 2 * radius))
stylesheet = self.styleSheet()
self.setStyleSheet(f"{stylesheet}border-radius: {radius}px;")

if color:
self.set_color(color)

def set_color(self, color: QColor | Qt.GlobalColor) -> None:
if isinstance(color, Qt.GlobalColor):
color = QColor(color)
style = f"background-color: rgb({color.red()}, {color.green()}, {color.blue()});"
self.setStyleSheet(f"{self.styleSheet()}{style}")


class CircleIndicator(Circle, IndicatorMixin):
def __init__(self, parent: Optional[QWidget] = None,
radius: int = 50) -> None:
super().__init__(parent, radius)
self.set_inactive()
4 changes: 4 additions & 0 deletions src/surface/gui/gui/widgets/debug_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
QVBoxLayout,
QWidget,
)
from gui.widgets.heartbeat import HeartbeatWidget


class DebugWidget(QWidget):
Expand All @@ -24,6 +25,9 @@ def __init__(self) -> None:
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
)

top_bar.addWidget(HeartbeatWidget(), alignment=Qt.AlignmentFlag.AlignTop |
Qt.AlignmentFlag.AlignLeft)

right_bar = QVBoxLayout()
right_bar.addWidget(ThrusterTester())
right_bar.addWidget(Arm())
Expand Down
28 changes: 20 additions & 8 deletions src/surface/gui/gui/widgets/flood_warning.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
QLabel,
QVBoxLayout,
QWidget,
QHBoxLayout
)

from gui.widgets.circle import CircleIndicator

from rov_msgs.msg import Flooding


class FloodWarning(QWidget):
signal: pyqtSignal = pyqtSignal(Flooding)

signal = pyqtSignal(Flooding)

def __init__(self) -> None:
super().__init__()
Expand All @@ -30,26 +34,34 @@ def __init__(self) -> None:
# Create a latch variable
self.warning_msg_latch: bool = False
# Create basic 2 vertical stacked boxes layout
self.flood_layout = QVBoxLayout()
flood_layout = QVBoxLayout()
# Create the label that tells us what this is
self.label = QLabel("Flooding Indicator")

header_layout = QHBoxLayout()
label = QLabel('Flooding Status')
font = QFont("Arial", 14)
self.label.setFont(font)
self.flood_layout.addWidget(self.label)
label.setFont(font)
header_layout.addWidget(label)
self.indicator_circle = CircleIndicator(radius=10)
header_layout.addWidget(self.indicator_circle)

flood_layout.addLayout(header_layout)

self.indicator = QLabel("No Water present")
self.indicator.setFont(font)
self.flood_layout.addWidget(self.indicator)
self.setLayout(self.flood_layout)
flood_layout.addWidget(self.indicator)
self.setLayout(flood_layout)

@pyqtSlot(Flooding)
def refresh(self, msg: Flooding) -> None:
if msg.flooding:
self.indicator.setText("FLOODING")
self.subscription.get_logger().error("Robot is actively flooding, do something!")
self.warning_msg_latch = True
self.indicator_circle.set_off()
else:
self.indicator.setText("No Water present")
self.indicator.setText('No Water present')
self.indicator_circle.set_on()
if self.warning_msg_latch:
self.subscription.get_logger().warning("Robot flooding has reset itself.")
self.warning_msg_latch = False
58 changes: 58 additions & 0 deletions src/surface/gui/gui/widgets/heartbeat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from gui.event_nodes.subscriber import GUIEventSubscriber
from gui.widgets.circle import CircleIndicator
from PyQt6.QtCore import pyqtSignal, pyqtSlot
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget

from rov_msgs.msg import VehicleState


class HeartbeatWidget(QWidget):

signal = pyqtSignal(VehicleState)

def __init__(self) -> None:
super().__init__()

self.signal.connect(self.refresh)
self.subscription = GUIEventSubscriber(VehicleState, 'vehicle_state_event', self.signal)
# Create a latch variable
self.warning_msg_latch: bool = False

heartbeat_layout = QVBoxLayout()

font = QFont("Arial", 14)

pi_status_layout = QHBoxLayout()
self.pi_indicator = QLabel('No Pi Status')
self.pi_indicator.setFont(font)
pi_status_layout.addWidget(self.pi_indicator)
self.pi_indicator_circle = CircleIndicator(radius=10)
pi_status_layout.addWidget(self.pi_indicator_circle)
heartbeat_layout.addLayout(pi_status_layout)

pixhawk_status_layout = QHBoxLayout()
self.pixhawk_indicator = QLabel('No Pixhawk Status')
self.pixhawk_indicator.setFont(font)
pixhawk_status_layout.addWidget(self.pixhawk_indicator)
self.pixhawk_indicator_circle = CircleIndicator(radius=10)
pixhawk_status_layout.addWidget(self.pixhawk_indicator_circle)
heartbeat_layout.addLayout(pixhawk_status_layout)

self.setLayout(heartbeat_layout)

@pyqtSlot(VehicleState)
def refresh(self, msg: VehicleState) -> None:
if msg.pi_connected:
self.pi_indicator.setText('Pi Connected')
self.pi_indicator_circle.set_on()
else:
self.pi_indicator.setText('Pi Disconnected')
self.pi_indicator_circle.set_off()

if msg.pixhawk_connected:
self.pixhawk_indicator.setText('Pixhawk Connected')
self.pixhawk_indicator_circle.set_on()
else:
self.pixhawk_indicator.setText('Pixhawk Disconnected')
self.pixhawk_indicator_circle.set_off()

0 comments on commit ff64521

Please sign in to comment.