Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bci gui rewrite #337

Merged
merged 82 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
eedb22f
adds seperate BCIUI file with stylsheet
Carsonthemonkey Jul 30, 2024
cb02d55
update demo experiment layout
Carsonthemonkey Aug 1, 2024
0b61d25
adjust stylesheet to remove label backgrounds
Carsonthemonkey Aug 5, 2024
c5542e1
improves gui code to allow for editing fields
Carsonthemonkey Aug 5, 2024
a69241d
add basic removeable element
Carsonthemonkey Aug 5, 2024
66644ce
add dynamic list class for easy ui reactivity
Carsonthemonkey Aug 5, 2024
e250982
Add small button component
Carsonthemonkey Aug 6, 2024
b950f9d
removes explicit color from small button
Carsonthemonkey Aug 6, 2024
fddf4d4
addds additional buttons to field items
Carsonthemonkey Aug 6, 2024
e32eed5
adds basic toggle class
Carsonthemonkey Aug 6, 2024
3365523
changes toggle componenet to be simple connecting function
Carsonthemonkey Aug 7, 2024
9e82c37
makes on off callbacks optional in `make_toggle`
Carsonthemonkey Aug 7, 2024
32c16d1
add toggle to optional button
Carsonthemonkey Aug 7, 2024
7f4f060
swaps confusing parameter names
Carsonthemonkey Aug 7, 2024
5429686
loads experiment fields into combobox
Carsonthemonkey Aug 7, 2024
71ffde9
adds field buttons
Carsonthemonkey Aug 7, 2024
cc7ba53
refactors gui to be inherited class
Carsonthemonkey Aug 8, 2024
0a4ea36
add simple `show_alert` method
Carsonthemonkey Aug 8, 2024
cfab919
add show_alert method
Carsonthemonkey Aug 8, 2024
502370f
adds validation to added fields
Carsonthemonkey Aug 8, 2024
79490cf
remove debug prints
Carsonthemonkey Aug 8, 2024
c4ce6e0
adds experiment creation to application
Carsonthemonkey Aug 8, 2024
1a77fc7
remove unused code
Carsonthemonkey Aug 9, 2024
9b645fb
rename `gui_rewrite` to `bciui`
Carsonthemonkey Aug 9, 2024
a6c7708
adds app start method
Carsonthemonkey Aug 9, 2024
ded7f92
adds vertical center option to bciui
Carsonthemonkey Aug 9, 2024
a5ebfa5
add beginning of intertask action ui
Carsonthemonkey Aug 9, 2024
44ee153
add progress text
Carsonthemonkey Aug 9, 2024
3b4ee34
allows for additional args in app running
Carsonthemonkey Aug 9, 2024
e376e0c
add functional progress bar
Carsonthemonkey Aug 9, 2024
76aa726
Add next task display and functioning buttons
Carsonthemonkey Aug 9, 2024
9e89ba2
adds seperate BCIUI file with stylsheet
Carsonthemonkey Jul 30, 2024
b39c1b4
update demo experiment layout
Carsonthemonkey Aug 1, 2024
d558ca1
adjust stylesheet to remove label backgrounds
Carsonthemonkey Aug 5, 2024
3f9c08e
improves gui code to allow for editing fields
Carsonthemonkey Aug 5, 2024
1b311d1
add basic removeable element
Carsonthemonkey Aug 5, 2024
9609421
add dynamic list class for easy ui reactivity
Carsonthemonkey Aug 5, 2024
ddd39c5
Add small button component
Carsonthemonkey Aug 6, 2024
bfb4b5c
removes explicit color from small button
Carsonthemonkey Aug 6, 2024
346f3db
addds additional buttons to field items
Carsonthemonkey Aug 6, 2024
3bf8e34
adds basic toggle class
Carsonthemonkey Aug 6, 2024
73dbeb6
changes toggle componenet to be simple connecting function
Carsonthemonkey Aug 7, 2024
ef5964f
makes on off callbacks optional in `make_toggle`
Carsonthemonkey Aug 7, 2024
79dfc47
add toggle to optional button
Carsonthemonkey Aug 7, 2024
2253c0f
swaps confusing parameter names
Carsonthemonkey Aug 7, 2024
014b280
loads experiment fields into combobox
Carsonthemonkey Aug 7, 2024
4f7403f
adds field buttons
Carsonthemonkey Aug 7, 2024
27499e2
refactors gui to be inherited class
Carsonthemonkey Aug 8, 2024
7dcbafb
add simple `show_alert` method
Carsonthemonkey Aug 8, 2024
67e6528
add show_alert method
Carsonthemonkey Aug 8, 2024
c727fe2
adds validation to added fields
Carsonthemonkey Aug 8, 2024
dd0aea5
remove debug prints
Carsonthemonkey Aug 8, 2024
4015f16
adds experiment creation to application
Carsonthemonkey Aug 8, 2024
be83c2b
remove unused code
Carsonthemonkey Aug 9, 2024
8131cc4
rename `gui_rewrite` to `bciui`
Carsonthemonkey Aug 9, 2024
ab447bd
adds app start method
Carsonthemonkey Aug 9, 2024
9723cea
adds vertical center option to bciui
Carsonthemonkey Aug 9, 2024
61013a7
add beginning of intertask action ui
Carsonthemonkey Aug 9, 2024
fbd9b1d
add progress text
Carsonthemonkey Aug 9, 2024
1e89e93
allows for additional args in app running
Carsonthemonkey Aug 9, 2024
6a5e726
add functional progress bar
Carsonthemonkey Aug 9, 2024
a09f844
Add next task display and functioning buttons
Carsonthemonkey Aug 9, 2024
a16e4ee
Merge branch 'bci_gui_rewrite' of https://github.com/CAMBI-tech/BciPy…
Carsonthemonkey Aug 10, 2024
fdba03e
remove unused imports
Carsonthemonkey Aug 26, 2024
6db009c
add protocol alyout and fix bug in DynamicList
Carsonthemonkey Aug 26, 2024
8f5605a
supports adding tasks to the interface
Carsonthemonkey Aug 28, 2024
3bb86e2
add reordering to tasks in the protocol
Carsonthemonkey Aug 29, 2024
6078525
allows adding new fields through the ui
Carsonthemonkey Aug 29, 2024
ac3cd92
adds saving protocol in experiment from gui
Carsonthemonkey Aug 29, 2024
76f18f5
lint gui code
Carsonthemonkey Aug 29, 2024
84bf3de
reduces font size
Carsonthemonkey Aug 29, 2024
0af7d85
Adds documentation to ui methods
Carsonthemonkey Aug 30, 2024
148b9d0
Update changelog
Carsonthemonkey Aug 30, 2024
90554c4
rename `intertask_action` to `intertask_gui`
Carsonthemonkey Aug 30, 2024
4a260ac
add action that displays intertask GUI
Carsonthemonkey Sep 5, 2024
8cb3dfb
Add proper types and docs to bciui
Carsonthemonkey Sep 5, 2024
2236b10
Deprecates old ExperimentRegistry in favor of new ui code
Carsonthemonkey Sep 5, 2024
30badae
Fixes incorrect background color when using dark mode
Carsonthemonkey Sep 5, 2024
81d619f
improve stylsheet loading to use existing bcipy constants
Carsonthemonkey Sep 5, 2024
0e63870
update legacy BCInterface to base session-orchestrator
Carsonthemonkey Sep 5, 2024
3f43224
Merge branch 'session-orchestrator' into bci_gui_rewrite
Carsonthemonkey Sep 5, 2024
3664c3d
lints code
Carsonthemonkey Sep 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# GUI Refactor

- Create new `BCIUI` class for simpler more straightforward UI creation.
- Create dedicated external stylesheet for global styling
- Rewrite Experiment Registry to use new GUI code
- Create intertask action UI

# Task Return Object
- Create `TaskData` dataclass to be returned from tasks
- updates task `execute` methods to return an instance of `TaskData`
Expand Down
44 changes: 44 additions & 0 deletions bcipy/gui/bcipy_stylesheet.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* This stylesheet uses the QSS syntax, but is named as a CSS file to take advantage of IDE CSS tooling */
Carsonthemonkey marked this conversation as resolved.
Show resolved Hide resolved
* {
background-color: white;
}
QWidget {
background-color: black;
color: white;
font-size: 14px;
}

QWidget > * {
background-color: none;
}

QPushButton {
background-color: green;
padding: 10px;
border-radius: 10px;
}


QPushButton[class="small-button"] {
padding: 5px;
border-radius: 5px;
}

QPushButton:hover {
background-color: darkgreen;
}

QPushButton:pressed {
background-color: darkslategrey;
}

QComboBox, QLineEdit {
background-color: white;
color: black;
padding: 5px;
border: none;
}

QScrollArea {
background-color: white;
}
226 changes: 226 additions & 0 deletions bcipy/gui/bciui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import os
Carsonthemonkey marked this conversation as resolved.
Show resolved Hide resolved
from typing import Callable
from PyQt6.QtCore import pyqtSignal
from PyQt6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QScrollArea,
QLayout,
QSizePolicy,
QMessageBox,
)
from typing import Optional, List
from bcipy.config import BCIPY_ROOT


class BCIUI(QWidget):
contents: QVBoxLayout
center_content_vertically: bool = False

def __init__(self, title: str = "BCIUI", default_width: int = 500, default_height: int = 600) -> None:
super().__init__()
self.resize(default_width, default_height)
self.setWindowTitle(title)
self.contents = QVBoxLayout()
self.setLayout(self.contents)

def app(self):
...

def apply_stylesheet(self) -> None:
# gui_dir = os.path.dirname(os.path.realpath(__file__))
gui_dir = f'{BCIPY_ROOT}/gui/'
stylesheet_path = os.path.join(gui_dir, "bcipy_stylesheet.css")
with open(stylesheet_path, "r") as f:
stylesheet = f.read()
self.setStyleSheet(stylesheet)

def display(self) -> None:
# Push contents to the top of the window
"""
Display the UI window and apply the stylesheet.
"""
self.app()
if not self.center_content_vertically:
self.contents.addStretch()
self.apply_stylesheet()
self.show()

def show_alert(self, alert_text: str) -> int:
"""
Shows an alert dialog with the specified text.

PARAMETERS
----------
:param: alert_text: string text to display in the alert dialog.
"""
msg = QMessageBox()
msg.setText(alert_text)
msg.setWindowTitle("Alert")
return msg.exec()

@staticmethod
def centered(widget: QWidget) -> QHBoxLayout:
layout = QHBoxLayout()
layout.addStretch()
layout.addWidget(widget)
layout.addStretch()
return layout

@staticmethod
def make_list_scroll_area(widget: QWidget) -> QScrollArea:
widget.setStyleSheet("background-color: transparent;")
scroll_area = QScrollArea()
scroll_area.setWidget(widget)
scroll_area.setWidgetResizable(True)
return scroll_area

@staticmethod
def make_toggle(
on_button: QPushButton,
off_button: QPushButton,
on_action: Optional[Callable] = lambda: None,
off_action: Optional[Callable] = lambda: None,
) -> None:
"""
Connects two buttons to toggle between eachother and call passed methods

PARAMETERS
----------
:param: on_button: QPushButton to toggle on
:param: off_button: QPushButton to toggle off
:param: on_action: function to call when on_button is clicked
:param: off_action: function to call when off_button is clicked

"""
off_button.hide()

def toggle_off():
on_button.hide()
off_button.show()
off_action()

def toggle_on():
on_button.show()
off_button.hide()
on_action()

on_button.clicked.connect(toggle_off)
off_button.clicked.connect(toggle_on)


class SmallButton(QPushButton):
"""A small button with a fixed size"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setProperty("class", "small-button")
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)


class DynamicItem(QWidget):
"""A widget that can be dynamically added and removed from the ui"""

on_remove: pyqtSignal = pyqtSignal()
data: dict = {}

def remove(self) -> None:
"""Remove the widget from it's parent DynamicList, removing it from the UI and deleting it"""
self.on_remove.emit()


class DynamicList(QWidget):
"""A list of QWidgets that can be dynamically updated"""

widgets: List[QWidget]

def __init__(self, layout: Optional[QLayout] = None):
super().__init__()
if layout is None:
layout = QVBoxLayout()
self.setLayout(layout)
self.widgets = []

def __len__(self):
return len(self.widgets)

def add_item(self, item: DynamicItem) -> None:
"""
Add a DynamicItem to the list.

PARAMETERS
----------
:param: item: DynamicItem to add to the list.
"""
self.widgets.append(item)
item.on_remove.connect(lambda: self.remove_item(item))
self.layout().addWidget(item)

def move_item(self, item: DynamicItem, new_index: int) -> None:
"""
Move a DynamicItem to a new index in the list.

PARAMETERS
----------
:param: item: A reference to the DynamicItem in the list to be moved.
:param: new_index: int new index to move the item to.
"""
if new_index < 0 or new_index >= len(self):
raise IndexError(f"Index out of range for length {len(self)}")

self.widgets.pop(self.widgets.index(item))
self.widgets.insert(new_index, item)
self.layout().removeWidget(item)
self.layout().insertWidget(new_index, item)

def index(self, item: DynamicItem) -> int:
"""
Get the index of a DynamicItem in the list.

PARAMETERS
----------
:param: item: A reference to the DynamicItem in the list to get the index of.

Returns
-------
The index of the item in the list.
"""
return self.widgets.index(item)

def remove_item(self, item: DynamicItem) -> None:
"""
Remove a DynamicItem from the list.

PARAMETERS
----------
:param: item: A reference to the DynamicItem to remove from the list
"""
self.widgets.remove(item)
self.layout().removeWidget(item)
item.deleteLater()

def clear(self) -> None:
"""Remove all items from the list"""
for widget in self.widgets:
self.layout().removeWidget(widget)
widget.deleteLater()
self.widgets = []

def list(self):
return [widget.data for widget in self.widgets]

def list_property(self, prop: str):
"""
Get a list of values for a given property of each DynamicItem's data dictionary.

PARAMETERS
----------
:param: prop: string property name to get the values of.

Returns
-------
A list of values for the given property.
"""
return [widget.data[prop] for widget in self.widgets]
Loading
Loading