diff --git a/src/software/thunderscope/gl/BUILD b/src/software/thunderscope/gl/BUILD
index 8bfb8a6d48..6c7ae2422f 100644
--- a/src/software/thunderscope/gl/BUILD
+++ b/src/software/thunderscope/gl/BUILD
@@ -13,6 +13,7 @@ py_library(
"//software/thunderscope/gl/layers:gl_measure_layer",
"//software/thunderscope/gl/widgets:gl_field_toolbar",
"//software/thunderscope/gl/widgets:gl_gamecontroller_toolbar",
+ "//software/thunderscope/gl/widgets:gl_simulated_test_toolbar",
"//software/thunderscope/replay:replay_controls",
requirement("pyqtgraph"),
],
diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py
index 28f64a299e..0275d18bec 100644
--- a/src/software/thunderscope/gl/gl_widget.py
+++ b/src/software/thunderscope/gl/gl_widget.py
@@ -21,6 +21,9 @@
from software.thunderscope.gl.widgets.gl_gamecontroller_toolbar import (
GLGamecontrollerToolbar,
)
+from software.thunderscope.gl.widgets.gl_simulated_test_toolbar import (
+ GLSimulatedTestToolbar,
+)
from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer
from proto.world_pb2 import SimulationState
from proto.replay_bookmark_pb2 import ReplayBookmark
@@ -113,7 +116,12 @@ def __init__(
friendly_color_yellow=friendly_color_yellow,
)
- self.__add_toolbar_toggle(self.gamecontroller_toolbar, "Gamecontroller")
+ self.simulated_test_toolbar = GLSimulatedTestToolbar(parent=self.gl_view_widget)
+
+ self.__add_toolbar_select(self.gamecontroller_toolbar, "Gamecontroller")
+ self.__add_toolbar_select(self.simulated_test_toolbar, "Tests")
+ self.current_toolbar = self.gamecontroller_toolbar
+ self.simulated_test_toolbar.setVisible(False)
# Setup replay controls if player is provided and the log has some size
self.player = player
@@ -269,6 +277,7 @@ def refresh(self) -> None:
if self.simulation_control_toolbar:
self.simulation_control_toolbar.refresh()
self.gamecontroller_toolbar.refresh()
+ self.simulated_test_toolbar.refresh()
simulation_state = self.simulation_state_buffer.get(block=False)
# Don't refresh the layers if the simulation is paused
@@ -322,22 +331,23 @@ def toggle_measure_mode(self) -> None:
else:
self.remove_layer(self.measure_layer)
- def __add_toolbar_toggle(self, toolbar: QWidget, name: str) -> None:
- """Adds a button to the toolbar menu to toggle the given toolbar
+ def __select_toolbar(self, toolbar: QWidget) -> None:
+ """Sets the currently selected toolbar to be only one visible
- :param toolbar: the toolbar to add the toggle button for
- :param name: the display name of the toolbar
+ :param toolbar: the toolbar to select
"""
- # Add a menu item for the Gamecontroller toolbar
- (toolbar_checkbox, toolbar_action) = self.__setup_menu_checkbox(
- name, self.toolbars_menu
- )
- self.toolbars_menu.addAction(toolbar_action)
+ self.gamecontroller_toolbar.setVisible(False)
+ self.simulated_test_toolbar.setVisible(False)
+ toolbar.setVisible(True)
+ self.current_toolbar = toolbar
- # Connect visibility of the toolbar to the menu item
- toolbar_checkbox.stateChanged.connect(
- lambda: toolbar.setVisible(toolbar_checkbox.isChecked())
- )
+ def __add_toolbar_select(self, toolbar: QWidget, name: str) -> None:
+ """Adds a button to the toolbar menu to select the given toolbar
+
+ :param toolbar: the toolbar to add the select button for
+ :param name: the display name of the toolbar
+ """
+ self.toolbars_menu.addAction(name, lambda: self.__select_toolbar(toolbar))
def __setup_menu_checkbox(
self, name: str, parent: QWidget, checked: bool = True
diff --git a/src/software/thunderscope/gl/widgets/BUILD b/src/software/thunderscope/gl/widgets/BUILD
index 1033dacfe6..41db91ddf5 100644
--- a/src/software/thunderscope/gl/widgets/BUILD
+++ b/src/software/thunderscope/gl/widgets/BUILD
@@ -62,3 +62,14 @@ py_library(
requirement("qtawesome"),
],
)
+
+py_library(
+ name = "gl_simulated_test_toolbar",
+ srcs = ["gl_simulated_test_toolbar.py"],
+ deps = [
+ ":gl_toolbar",
+ "//software/thunderscope/common:common_widgets",
+ requirement("pyqtgraph"),
+ requirement("qtawesome"),
+ ],
+)
diff --git a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py
index d1aed796fd..fdf2bee6c7 100644
--- a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py
+++ b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py
@@ -2,7 +2,7 @@
from pyqtgraph.Qt import QtGui
from proto.import_all_protos import *
from proto.ssl_gc_common_pb2 import Team as SslTeam
-from typing import Callable, override
+from typing import override
import webbrowser
from software.thunderscope.gl.widgets.gl_runtime_selector import GLRuntimeSelectorDialog
from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar
@@ -52,28 +52,28 @@ def __init__(
self.friendly_color_yellow = friendly_color_yellow
# Setup Stop button for sending the STOP gamecontroller command
- self.stop_button = self.__setup_icon_button(
+ self.stop_button = self.setup_icon_button(
qta.icon("fa6s.pause"),
"Stops gameplay, robots form circle around ball",
self.__send_stop_command,
)
# Setup Force Start button for sending the FORCE_START gamecontroller command
- self.force_start_button = self.__setup_icon_button(
+ self.force_start_button = self.setup_icon_button(
qta.icon("ph.arrow-u-up-right-fill"),
"Force Start, restarts the game",
self.__send_force_start_command,
)
# Setup Halt button for sending the HALT gamecontroller command
- self.halt_button = self.__setup_icon_button(
+ self.halt_button = self.setup_icon_button(
qta.icon("fa5s.stop"),
"Halt, stops all robots immediately",
self.__send_halt_command,
)
# Setup Normal Start button for sending the NORMAL_START gamecontroller command
- self.normal_start_button = self.__setup_icon_button(
+ self.normal_start_button = self.setup_icon_button(
qta.icon("fa5s.play"),
"Normal Start, resumes game from a set play (disabled when no play selected)",
self.__send_normal_start_command,
@@ -92,7 +92,7 @@ def __init__(
self.plays_menu.addSeparator()
self.__add_plays_menu_items(is_blue=False)
- self.gc_browser_button = self.__setup_icon_button(
+ self.gc_browser_button = self.setup_icon_button(
qta.icon("mdi6.open-in-new"),
"Opens the SSL Gamecontroller in a browser window",
lambda: webbrowser.open(self.GAME_CONTROLLER_URL, new=0, autoraise=True),
@@ -118,14 +118,14 @@ def __init__(
self.__toggle_normal_start_button()
self.layout().addWidget(QLabel("Gamecontroller"))
- self.__add_separator(self.layout())
+ self.add_separator(self.layout())
self.layout().addWidget(self.stop_button)
self.layout().addWidget(self.halt_button)
self.layout().addWidget(self.force_start_button)
- self.__add_separator(self.layout())
+ self.add_separator(self.layout())
self.layout().addWidget(self.plays_menu_button)
self.layout().addWidget(self.normal_start_button)
- self.__add_separator(self.layout())
+ self.add_separator(self.layout())
self.layout().addWidget(self.gc_browser_button)
self.layout().addStretch()
self.__add_separator(self.layout())
@@ -137,15 +137,6 @@ def refresh(self) -> None:
"""Refreshes the UI to update toolbar position"""
self.move(0, self.parentWidget().geometry().bottom() - self.height())
- def __add_separator(self, layout: QBoxLayout) -> None:
- """Adds a separator line with enough spacing to the given layout
-
- :param layout: the layout to add the separator to
- """
- layout.addSpacing(10)
- layout.addWidget(QLabel("|"))
- layout.addSpacing(10)
-
def __add_plays_menu_items(self, is_blue: bool) -> None:
"""Initializes the plays menu with the available plays for the given team
@@ -210,31 +201,6 @@ def __toggle_normal_start_button(self) -> None:
)
)
- def __setup_icon_button(
- self,
- icon: QtGui.QPixmap,
- tooltip: str,
- callback: Callable[[], None],
- display_text: str = None,
- ) -> QPushButton:
- """Sets up a button with the given name and callback
-
- :param icon: the icon displayed on the button
- :param tooltip: the tooltip displayed when hovering over the button
- :param callback: the callback for the button click
- :param display_text: optional param if button needs both text and an icon
- :return: the button
- """
- button = QPushButton()
- button.setIcon(icon)
- button.setToolTip(tooltip)
- button.setStyleSheet(self.get_button_style())
- button.clicked.connect(callback)
-
- if display_text:
- button.setText(display_text)
- return button
-
def __send_stop_command(self) -> None:
"""Sends a STOP command to the gamecontroller"""
self.__send_gc_command(Command.Type.STOP, SslTeam.UNKNOWN)
diff --git a/src/software/thunderscope/gl/widgets/gl_simulated_test_toolbar.py b/src/software/thunderscope/gl/widgets/gl_simulated_test_toolbar.py
new file mode 100644
index 0000000000..fd1b3f1fe5
--- /dev/null
+++ b/src/software/thunderscope/gl/widgets/gl_simulated_test_toolbar.py
@@ -0,0 +1,33 @@
+from pyqtgraph.Qt import QtWidgets
+from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar
+import qtawesome as qta
+from typing import override
+
+
+class GLSimulatedTestToolbar(GLToolbar):
+ """A toolbar with controls to run simulated tests within Thunderscope"""
+
+ def __init__(self, parent: QtWidgets.QWidget):
+ """Initializes the toolbar and constructs its layout
+
+ :param parent: the parent to overlay this toolbar over
+ """
+ super(GLSimulatedTestToolbar, self).__init__(parent=parent)
+
+ self.run_test_button = self.setup_icon_button(
+ qta.icon("fa5s.play"),
+ "Runs simluated test",
+ self.__run_test,
+ )
+
+ self.layout().addWidget(QtWidgets.QLabel("Simulated Tests"))
+ self.add_separator(self.layout())
+ self.layout().addWidget(self.run_test_button)
+
+ @override
+ def refresh(self) -> None:
+ """Refreshes the UI to update toolbar position"""
+ self.move(0, self.parentWidget().geometry().bottom() - self.height())
+
+ def __run_test(self):
+ print("RUN TEST")
diff --git a/src/software/thunderscope/gl/widgets/gl_toolbar.py b/src/software/thunderscope/gl/widgets/gl_toolbar.py
index 06963ae8da..265943ad0e 100644
--- a/src/software/thunderscope/gl/widgets/gl_toolbar.py
+++ b/src/software/thunderscope/gl/widgets/gl_toolbar.py
@@ -1,6 +1,7 @@
import textwrap
-from pyqtgraph.Qt import QtCore
+from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.Qt.QtWidgets import *
+from typing import Callable
class GLToolbar(QWidget):
@@ -54,3 +55,37 @@ def get_button_style(self, is_enabled: bool = True) -> str:
}}
"""
)
+
+ def setup_icon_button(
+ self,
+ icon: QtGui.QPixmap,
+ tooltip: str,
+ callback: Callable[[], None],
+ display_text: str = None,
+ ) -> QPushButton:
+ """Sets up a button with the given name and callback
+
+ :param icon: the icon displayed on the button
+ :param tooltip: the tooltip displayed when hovering over the button
+ :param callback: the callback for the button click
+ :param display_text: optional param if button needs both text and an icon
+ :return: the button
+ """
+ button = QPushButton()
+ button.setIcon(icon)
+ button.setToolTip(tooltip)
+ button.setStyleSheet(self.get_button_style())
+ button.clicked.connect(callback)
+
+ if display_text:
+ button.setText(display_text)
+ return button
+
+ def add_separator(self, layout: QBoxLayout) -> None:
+ """Adds a separator line with enough spacing to the given layout
+
+ :param layout: the layout to add the separator to
+ """
+ layout.addSpacing(10)
+ layout.addWidget(QLabel("|"))
+ layout.addSpacing(10)