Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
153 commits
Select commit Hold shift + click to select a range
bf12355
build: PySide6-QtAds dependency added
wyzula-jan Aug 7, 2025
e4fb126
fix(bec_connector): dedicated remove signal added for listeners
wyzula-jan Aug 5, 2025
5a49c41
fix(bec_connector): added name established signal for listeners
wyzula-jan Aug 6, 2025
8f359f8
refactor(bec_connector): signals renamed
wyzula-jan Aug 19, 2025
00c0232
feat(widget_io): widget hierarchy can grap all bec connectors from th…
wyzula-jan Aug 6, 2025
faa0aa6
feat(widget_io): widget hierarchy find_ancestor added
wyzula-jan Aug 7, 2025
77295f1
refactor(widget_io): ancestor hierarchy methods consolidated
wyzula-jan Aug 19, 2025
5ecdaf4
fix(widget_state_manager): state manager can save all properties recu…
wyzula-jan Aug 6, 2025
a58fb3f
fix(widget_state_manager): state manager can save to already existing…
wyzula-jan Aug 6, 2025
6643dc2
feat(bec_widget): attach/detach method for all widgets + client regen…
wyzula-jan Aug 7, 2025
24f54fc
refactor(bec_main_window): main app theme renamed to View
wyzula-jan Aug 13, 2025
cbcf239
feat(advanced_dock_area): added ads based dock area with profiles
wyzula-jan Aug 5, 2025
4e96d6c
fix(bec_widgets): by default the linux display manager is switched to…
wyzula-jan Aug 14, 2025
6b1b7a6
refactor(advanced_dock_area): ads changed to separate widget
wyzula-jan Aug 15, 2025
9b9b8ab
feat(advanced_dock_area): ads has default direction
wyzula-jan Aug 18, 2025
d4ea9f2
fix(advanced_dock_area): dock manager global flags initialised in BW …
wyzula-jan Aug 18, 2025
66f01dc
refactor(advanced_dock_area): profile tools moved to separate module
wyzula-jan Aug 19, 2025
63b7f92
build(bec_qthemes): version 1.0 dependency
wyzula-jan Aug 21, 2025
138fd1b
fix(bec_widgets): adapt to bec_qthemes 1.0
wyzula-jan Aug 19, 2025
125c94c
fix:queue abort button fixed
wyzula-jan Aug 25, 2025
dcdf5a4
fix(toolbar): toolbar menu button fixed
wyzula-jan Aug 25, 2025
b6f8866
fix: device combobox change paint event to stylesheet change
wyzula-jan Aug 25, 2025
43d4927
fix: tree items due to pushbutton margins
wyzula-jan Aug 25, 2025
03fe3a8
fix: remove pyqtgraph styling logic
wyzula-jan Aug 25, 2025
daeec7e
fix: compact popup layout spacing
wyzula-jan Aug 25, 2025
3e23ba9
chore: fix formatter
wakonig Aug 25, 2025
b2d6b8d
fix(compact_popup): import from qtpy instead of pyside6
wakonig Aug 25, 2025
b029945
build: add missing darkdetect dependency
wakonig Aug 25, 2025
d21464f
test: fixes after theme changes
wakonig Aug 26, 2025
bdd732b
ci: add artifact upload
wakonig Aug 26, 2025
0daeb38
test: fix tests for qtheme v1
wakonig Aug 26, 2025
3dba832
fix(BECWidget): ensure that theme changes are only triggered from ali…
wakonig Aug 26, 2025
39809e8
fix(themes): move apply theme from BECWidget class to server init
wakonig Aug 28, 2025
8c226b6
refactor(spinner): improve enum access
wakonig Aug 28, 2025
e0d65b4
test: apply theme on qapp creation
wakonig Aug 28, 2025
2aef050
refactor: move to qthemes 1.1.2
wakonig Aug 28, 2025
fa642aa
fix: process all deletion events before applying a new theme.
wakonig Aug 30, 2025
c72789c
feat: add SafeConnect
wakonig Aug 29, 2025
66a29d1
test: remove outdated tests
wakonig Sep 1, 2025
3a78c1b
feat(main_app): main app with interactive app switcher
wyzula-jan Sep 4, 2025
67d50d7
feat(main_app):views with examples for enter and exit hook
wyzula-jan Sep 10, 2025
c20bc00
test(main_app): test extended
wyzula-jan Sep 10, 2025
6f78c91
fix(colors): accent colors fetching if theme not provided
wyzula-jan Sep 11, 2025
5895147
feat(dm-view): initial commit for config_view, ophyd_test and dm_widget
cappel89 Aug 22, 2025
7935b9b
refactor: refactor device_manager_view
cappel89 Aug 27, 2025
17bccb8
chore: update qtmonaco dependency
d-perl Sep 3, 2025
73731c8
feat: add ListOfExpandableFrames util
d-perl Aug 27, 2025
03441e9
feat: add available device resource browser
d-perl Sep 2, 2025
7f77b9b
fix(dm): add constants.py
d-perl Sep 2, 2025
42fc7c8
feat: add available devices to manager view
d-perl Sep 2, 2025
4ada308
feat(device_table): prepare table for drop action
d-perl Sep 2, 2025
72c86b9
feat: prepare available devices for dragging config
d-perl Sep 2, 2025
d467ecc
fix: allow setting state with other conformation of config
d-perl Sep 2, 2025
7676e86
feat: connect config update to available devices
d-perl Sep 2, 2025
be81232
feat: add shared selection signal util
d-perl Sep 2, 2025
a998793
feat(dm): apply shared selection signal util to view
d-perl Sep 2, 2025
a4e0246
refactor: util for MimeData
d-perl Sep 2, 2025
f41ea59
feat: add/remove functionality for device table
d-perl Sep 2, 2025
24c213a
feat: connect available devices to doc and yaml views
d-perl Sep 4, 2025
5281d38
fix: add all devices to test list
d-perl Sep 4, 2025
cac1066
refactor: available devices add+remove from toolbar
d-perl Sep 4, 2025
9f75599
fix: device table theming
d-perl Sep 5, 2025
97fddf0
refactor: genericise config form
d-perl Sep 5, 2025
5fde098
feat(device_manager): add device dialog with presets
d-perl Sep 5, 2025
bd9be10
fix: don't use deprecated api for CDockWidget
d-perl Sep 8, 2025
7e3de2c
fix: slightly improve theming
d-perl Sep 8, 2025
f7f3750
style: typo
d-perl Sep 8, 2025
9cd4e10
feat: allow setting config in redis
d-perl Sep 10, 2025
0cad33c
fix: check plugin exists before loading
d-perl Sep 15, 2025
fb2dfff
refactor: redo device tester
d-perl Sep 10, 2025
421e6d3
style: imports
d-perl Sep 15, 2025
ca9007a
fix: tests
d-perl Sep 15, 2025
d8900a5
fix: preset classes for config dialog
d-perl Sep 16, 2025
12a454f
refactor: cleanup
cappel89 Sep 30, 2025
1b7f9a3
fix: mark processEvents for checks
cappel89 Sep 30, 2025
016e1af
feat(actions): actions can be created with label text with beside or …
wyzula-jan Oct 2, 2025
99007d8
feat(main_app): device manager implemented into main app
wyzula-jan Sep 30, 2025
657648c
refactor(examples): wrong main app removed
wyzula-jan Sep 30, 2025
718f456
fix(device_manager_view): removed custom styling for overlay
wyzula-jan Sep 30, 2025
fcd7b3d
perf(device_table_view): text wrapper delegate removed since it was n…
wyzula-jan Sep 30, 2025
bd9db4f
fix(available_device_resources): top toolbar size fixed
wyzula-jan Sep 30, 2025
91e8a72
refactor(device_manager_view): added labels to main toolbar
wyzula-jan Oct 2, 2025
e52afd5
feat(busy_loader): busy loader added to bec widget base class
wyzula-jan Sep 24, 2025
8b92953
test(busy_loader): tests added
wyzula-jan Sep 29, 2025
c609a2c
test(device_input_base): added qtbot
wyzula-jan Sep 29, 2025
60c207f
test(color_utils): cleanup for pyqtgraph
wyzula-jan Oct 3, 2025
de5c2e3
fix(client): abort, reset, stop button removed from RPC access
wyzula-jan Oct 3, 2025
b2af9c4
fix(signal_label): dispatcher unsubscribed in the cleanup
wyzula-jan Oct 3, 2025
8e66bae
feat(help-inspector): add help inspector widget
cappel89 Oct 7, 2025
cbf9d21
fix(advanced_dock_area): vs code removed from available docks
wyzula-jan Oct 7, 2025
c97c50f
fix(web_console): added startup kwarg
wyzula-jan Oct 7, 2025
caaa9e5
fix(advanced_dock_area): added terminal,bec_shell, removed VSCode
wyzula-jan Oct 7, 2025
4e38365
feat(advanced_dock_area): add 2D positioner box to device actions
wyzula-jan Oct 10, 2025
979de35
test(advanced_dock_area): tests fixed
wyzula-jan Oct 10, 2025
3522cab
refactor: improve device-manager-view
cappel89 Oct 3, 2025
bc77d31
refactor(device-manager-view): cleanup, minor bugfixes
cappel89 Oct 13, 2025
b81e92b
test(device-manager-components): test for device manager components
cappel89 Oct 13, 2025
bb363d8
test(device-manager-view): Add test for the device-manager-view initi…
cappel89 Oct 14, 2025
a57d3c6
refactor: improve test coverage for help_inspector and device_manager…
cappel89 Oct 14, 2025
140f126
feat(ads): add pyi stub file to provide type hints for ads
wakonig Oct 17, 2025
6b5aa74
fix(toolbar): create_action_with_text text will not change to tooltip…
wyzula-jan Oct 27, 2025
16d88fe
fix(jupyter_console): cleanup
wyzula-jan Oct 17, 2025
cb233b8
feat(jupyter_console_window): adjustment for general usage
wyzula-jan Oct 17, 2025
4dc128c
fix(actions): set default text position to 'under'
wakonig Oct 30, 2025
b43abfa
ci: install ttyd
wakonig Oct 15, 2025
7a43111
feat: add developer view
wyzula-jan Aug 18, 2025
752d9ba
refactor: move splitter weights to view; change developerview to view…
wakonig Oct 17, 2025
7cd4f44
fix(view): apply view_id and view_title attributes to existing ViewBa…
wakonig Oct 8, 2025
da1a3dd
fix(dependencies): add copier and typer to project dependencies
wakonig Oct 14, 2025
0d0eb1d
fix: developer view improvements and dependency updates
wakonig Oct 8, 2025
96e7ca3
fix: add metadata to scan control export
wakonig Oct 30, 2025
4284d57
docs(monaco_dock): add missing argument
wakonig Oct 30, 2025
e1e0fbd
feat(guided_tour): add guided tour
wakonig Oct 27, 2025
fa5135d
feat(guided_tour): added option to register QActions from toolbar
wyzula-jan Oct 31, 2025
79d64c4
refactor(developer_view): developer view moved from examples to view …
wyzula-jan Nov 4, 2025
8776784
refactor(collapsible_tree_section): change add pushbuttons to qtoolbu…
wyzula-jan Nov 4, 2025
7f488c0
fix(becconnector): ophyd thread killer on exit + in conftest
wyzula-jan Oct 28, 2025
0ba14b2
WILL BE REMOVED AFTER REBASE build: pyqtgraph pin to 0.13.7
wyzula-jan Nov 17, 2025
937bea5
WILL BE REMOVED AFTER REBASE fix(test): removed duplicate test in cro…
wyzula-jan Nov 17, 2025
38134ec
WILL BE REMOVED AFTER REBASE fix(fakeredis): add support for addition…
wakonig Nov 17, 2025
5b364c5
refactor: improve toolbar actions typing
d-perl Nov 18, 2025
578be47
feat(bec_widget): save screenshot to bytes
wyzula-jan Oct 20, 2025
1ab9154
fix(widget_state_manager): added shiboken check
wyzula-jan Nov 9, 2025
a20d8b2
fix(dark_mode_button): skip settings added
wyzula-jan Nov 9, 2025
652bf2d
fix(main_window): cleanup adjusted with shiboken6
wyzula-jan Oct 26, 2025
b964369
feat(advanced_dock_area): UI/UX for profile management improved, savi…
wyzula-jan Oct 6, 2025
003fff7
fix(advanced_dock_area): disable developer mode switch
wyzula-jan Oct 27, 2025
28c0b3b
fix(main_window): removed general forced cleanup
wyzula-jan Nov 7, 2025
d259de2
feat(advanced_dock_area): created DockAreaWidget base class; profile …
wyzula-jan Nov 4, 2025
6c6408e
refactor(monaco_dock): changed to use DockAreaWidget
wyzula-jan Nov 5, 2025
00e4786
refactor(developer_view): changed to use DockAreaWidget
wyzula-jan Nov 5, 2025
bd2d47f
refactor(main_app): adapted for DockAreaWidget changes
wyzula-jan Nov 9, 2025
22651f1
fix(widget_state_manager): visible is always true
wyzula-jan Nov 19, 2025
0119332
fix(ide_explorer): light mode fixed
wyzula-jan Nov 19, 2025
a9039c5
fix(qt_ads): pythons stubs match structure of PySide6QtAds
wyzula-jan Nov 19, 2025
86a966f
fix(widget_io): find ancestor returns correct type
wyzula-jan Nov 19, 2025
31d01ab
fix(bec_widget): improved qt enums; grab safeguard
wyzula-jan Nov 19, 2025
557371f
fix(client): client regenerated
wyzula-jan Nov 10, 2025
aed22c6
fix(widget_state_manager): always setting visible to true
wyzula-jan Nov 20, 2025
1b299b9
feat(widget_state_manager): can serialize from root
wyzula-jan Nov 20, 2025
e74eea1
fix(widget_state_manager): skip property listed introduced
wyzula-jan Nov 20, 2025
e708ee5
fix(widget_state_manager): IDEExplorer plugin not initialised in desi…
wyzula-jan Nov 20, 2025
0f392ef
fix(widgets): removed isVisible from all SafeProperties
wyzula-jan Nov 20, 2025
fd2b891
feat(advanced_dock_area): instance lock for multiple ads in same session
wyzula-jan Nov 20, 2025
dc30e6b
fix(widget_state_manager): visibility managed by parent
wyzula-jan Nov 20, 2025
78485d3
fix(widget_state_manager): omits QIcon properties to prevent segfault
wyzula-jan Nov 21, 2025
f9c03f1
refactor: improvements to enum access
wakonig Nov 23, 2025
d090f8f
refactor(developer widget): type hint improvements
wakonig Nov 23, 2025
c2780d6
feat(advanced_dock_area): floating docks restore with relative geometry
wyzula-jan Nov 21, 2025
73528ee
fix(collapsible_tree_section): fix typo in the styleSheet
wyzula-jan Nov 24, 2025
5c9c281
feat(hover_gradient): gradient mouse tracking utility for QFrame widgets
wyzula-jan Jun 24, 2025
644e4ec
wip opacity as separate parameter
wyzula-jan Jun 24, 2025
37c7650
wip migrated hover gradient from bec widgets - removed completely als…
wyzula-jan Nov 25, 2025
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
1 change: 1 addition & 0 deletions .github/actions/bw_install/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ runs:
sudo apt-get update
sudo apt-get install -y libgl1 libegl1 x11-utils libxkbcommon-x11-0 libdbus-1-3 xvfb
sudo apt-get -y install libnss3 libxdamage1 libasound2t64 libatomic1 libxcursor1
sudo apt-get -y install ttyd

- name: Install Python dependencies
shell: bash
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ jobs:
id: coverage
run: pytest --random-order --cov=bec_widgets --cov-config=pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail tests/unit_tests/

- name: Upload test artifacts
uses: actions/upload-artifact@v4
if: failure()
with:
name: image-references
path: bec_widgets/tests/reference_failures/
if-no-files-found: ignore

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
Expand Down
15 changes: 15 additions & 0 deletions bec_widgets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
import os
import sys

import bec_widgets.widgets.containers.qt_ads as QtAds
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot

if sys.platform.startswith("linux"):
qt_platform = os.environ.get("QT_QPA_PLATFORM", "")
if qt_platform != "offscreen":
os.environ["QT_QPA_PLATFORM"] = "xcb"

# Default QtAds configuration
QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.eConfigFlag.FocusHighlighting, True)
QtAds.CDockManager.setConfigFlag(
QtAds.CDockManager.eConfigFlag.RetainTabSizeWhenCloseButtonHidden, True
)

__all__ = ["BECWidget", "SafeSlot", "SafeProperty"]
28 changes: 25 additions & 3 deletions bec_widgets/applications/launch_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,31 @@
from typing import TYPE_CHECKING, Callable

from bec_lib.logger import bec_logger
from bec_qthemes import enable_hover_gradient
from qtpy.QtCore import Qt, Signal # type: ignore
from qtpy.QtGui import QFontMetrics, QPainter, QPainterPath, QPixmap
from qtpy.QtWidgets import (
QApplication,
QComboBox,
QFileDialog,
QFrame,
QHBoxLayout,
QLabel,
QPushButton,
QSizePolicy,
QSpacerItem,
QVBoxLayout,
QWidget,
)

import bec_widgets
from bec_widgets.cli.rpc.rpc_register import RPCRegister
from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widgets
from bec_widgets.utils.colors import apply_theme
from bec_widgets.utils.container_utils import WidgetContainerUtils
from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.name_utils import pascal_to_snake
from bec_widgets.utils.plugin_utils import get_plugin_auto_updates
from bec_widgets.utils.round_frame import RoundedFrame
from bec_widgets.utils.toolbars.toolbar import ModularToolBar
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
Expand All @@ -43,7 +46,7 @@
MODULE_PATH = os.path.dirname(bec_widgets.__file__)


class LaunchTile(RoundedFrame):
class LaunchTile(QFrame):
DEFAULT_SIZE = (250, 300)
open_signal = Signal()

Expand All @@ -56,8 +59,14 @@ def __init__(
description: str | None = None,
show_selector: bool = False,
tile_size: tuple[int, int] | None = None,
gradient: list[str] | None = None,
):
super().__init__(parent=parent, orientation="vertical")
super().__init__(parent=parent)
self.setProperty("skip_settings", True)
self.setProperty("variant", "tile")
self.setAttribute(Qt.WA_StyledBackground, True)
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(5, 5, 5, 5)

# Provide a per‑instance TILE_SIZE so the class can compute layout
if tile_size is None:
Expand Down Expand Up @@ -153,6 +162,12 @@ def __init__(
"""
)
self.layout.addWidget(self.action_button, alignment=Qt.AlignCenter)
if gradient is not None:
enable_hover_gradient(self, colours=gradient)

def apply_theme(self, theme: str):
"""Allow tiles to be theme-aware without custom styling logic."""
self.update()

def _fit_label_to_width(self, label: QLabel, max_width: int, min_pt: int = 10):
"""
Expand Down Expand Up @@ -215,6 +230,7 @@ def __init__(
description="Highly flexible and customizable dock area application with modular widgets.",
action_button=lambda: self.launch("dock_area"),
show_selector=False,
gradient=["#B73665", "#232770"],
)

self.available_auto_updates: dict[str, type[AutoUpdates]] = (
Expand All @@ -229,6 +245,7 @@ def __init__(
action_button=self._open_auto_update,
show_selector=True,
selector_items=list(self.available_auto_updates.keys()) + ["Default"],
gradient=["#EE0678", "#FF6A00"],
)

self.register_tile(
Expand All @@ -239,6 +256,7 @@ def __init__(
description="GUI application with custom UI file.",
action_button=self._open_custom_ui_file,
show_selector=False,
gradient=["#155799", "#179655"],
)

# plugin widgets
Expand All @@ -257,6 +275,7 @@ def __init__(
action_button=self._open_widget,
show_selector=True,
selector_items=list(self.available_widgets.keys()),
gradient=["#000046", "#1CB5E0"],
)

self._update_theme()
Expand All @@ -275,6 +294,7 @@ def register_tile(
action_button: Callable | None = None,
show_selector: bool = False,
selector_items: list[str] | None = None,
gradient: list[str] | None = None,
):
"""
Register a tile in the launcher window.
Expand All @@ -297,6 +317,7 @@ def register_tile(
description=description,
show_selector=show_selector,
tile_size=self.TILE_SIZE,
gradient=gradient,
)
tile.setFixedWidth(self.TILE_SIZE[0])
tile.setMinimumHeight(self.TILE_SIZE[1])
Expand Down Expand Up @@ -585,6 +606,7 @@ def closeEvent(self, event):
import sys

app = QApplication(sys.argv)
apply_theme("dark")
launcher = LaunchWindow()
launcher.show()
sys.exit(app.exec())
229 changes: 229 additions & 0 deletions bec_widgets/applications/main_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
from qtpy.QtWidgets import QApplication, QHBoxLayout, QStackedWidget, QWidget

from bec_widgets.applications.navigation_centre.reveal_animator import ANIMATION_DURATION
from bec_widgets.applications.navigation_centre.side_bar import SideBar
from bec_widgets.applications.navigation_centre.side_bar_components import NavigationItem
from bec_widgets.applications.views.developer_view.developer_view import DeveloperView
from bec_widgets.applications.views.device_manager_view.device_manager_widget import (
DeviceManagerWidget,
)
from bec_widgets.applications.views.view import ViewBase, WaveformViewInline, WaveformViewPopup
from bec_widgets.utils.colors import apply_theme
from bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area import AdvancedDockArea
from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow


class BECMainApp(BECMainWindow):

def __init__(
self,
parent=None,
*args,
anim_duration: int = ANIMATION_DURATION,
show_examples: bool = False,
**kwargs,
):
super().__init__(parent=parent, *args, **kwargs)
self._show_examples = bool(show_examples)

# --- Compose central UI (sidebar + stack)
self.sidebar = SideBar(parent=self, anim_duration=anim_duration)
self.stack = QStackedWidget(self)

container = QWidget(self)
layout = QHBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(self.sidebar, 0)
layout.addWidget(self.stack, 1)
self.setCentralWidget(container)

# Mapping for view switching
self._view_index: dict[str, int] = {}
self._current_view_id: str | None = None
self.sidebar.view_selected.connect(self._on_view_selected)

self._add_views()

def _add_views(self):
self.add_section("BEC Applications", "bec_apps")
self.ads = AdvancedDockArea(
self, profile_namespace="main_workspace", auto_profile_namespace=False
)
self.ads.setObjectName("MainWorkspace")
self.device_manager = DeviceManagerWidget(self)
self.developer_view = DeveloperView(self)

self.add_view(
icon="widgets", title="Dock Area", id="dock_area", widget=self.ads, mini_text="Docks"
)
self.add_view(
icon="display_settings",
title="Device Manager",
id="device_manager",
widget=self.device_manager,
mini_text="DM",
)
self.add_view(
icon="code_blocks",
title="IDE",
widget=self.developer_view,
id="developer_view",
exclusive=True,
)

if self._show_examples:
self.add_section("Examples", "examples")
waveform_view_popup = WaveformViewPopup(
parent=self, id="waveform_view_popup", title="Waveform Plot"
)
waveform_view_stack = WaveformViewInline(
parent=self, id="waveform_view_stack", title="Waveform Plot"
)

self.add_view(
icon="show_chart",
title="Waveform With Popup",
id="waveform_popup",
widget=waveform_view_popup,
mini_text="Popup",
)
self.add_view(
icon="show_chart",
title="Waveform InLine Stack",
id="waveform_stack",
widget=waveform_view_stack,
mini_text="Stack",
)

self.set_current("dock_area")
self.sidebar.add_dark_mode_item()

# --- Public API ------------------------------------------------------
def add_section(self, title: str, id: str, position: int | None = None):
return self.sidebar.add_section(title, id, position)

def add_separator(self):
return self.sidebar.add_separator()

def add_dark_mode_item(self, id: str = "dark_mode", position: int | None = None):
return self.sidebar.add_dark_mode_item(id=id, position=position)

def add_view(
self,
*,
icon: str,
title: str,
id: str,
widget: QWidget,
mini_text: str | None = None,
position: int | None = None,
from_top: bool = True,
toggleable: bool = True,
exclusive: bool = True,
) -> NavigationItem:
"""
Register a view in the stack and create a matching nav item in the sidebar.

Args:
icon(str): Icon name for the nav item.
title(str): Title for the nav item.
id(str): Unique ID for the view/item.
widget(QWidget): The widget to add to the stack.
mini_text(str, optional): Short text for the nav item when sidebar is collapsed.
position(int, optional): Position to insert the nav item.
from_top(bool, optional): Whether to count position from the top or bottom.
toggleable(bool, optional): Whether the nav item is toggleable.
exclusive(bool, optional): Whether the nav item is exclusive.

Returns:
NavigationItem: The created navigation item.


"""
item = self.sidebar.add_item(
icon=icon,
title=title,
id=id,
mini_text=mini_text,
position=position,
from_top=from_top,
toggleable=toggleable,
exclusive=exclusive,
)
# Wrap plain widgets into a ViewBase so enter/exit hooks are available
if isinstance(widget, ViewBase):
view_widget = widget
view_widget.view_id = id
view_widget.view_title = title
else:
view_widget = ViewBase(content=widget, parent=self, id=id, title=title)

idx = self.stack.addWidget(view_widget)
self._view_index[id] = idx
return item

def set_current(self, id: str) -> None:
if id in self._view_index:
self.sidebar.activate_item(id)

# Internal: route sidebar selection to the stack
def _on_view_selected(self, vid: str) -> None:
# Determine current view
current_index = self.stack.currentIndex()
current_view = (
self.stack.widget(current_index) if 0 <= current_index < self.stack.count() else None
)

# Ask current view whether we may leave
if current_view is not None and hasattr(current_view, "on_exit"):
may_leave = current_view.on_exit()
if may_leave is False:
# Veto: restore previous highlight without re-emitting selection
if self._current_view_id is not None:
self.sidebar.activate_item(self._current_view_id, emit_signal=False)
return

# Proceed with switch
idx = self._view_index.get(vid)
if idx is None or not (0 <= idx < self.stack.count()):
return
self.stack.setCurrentIndex(idx)
new_view = self.stack.widget(idx)
self._current_view_id = vid
if hasattr(new_view, "on_enter"):
new_view.on_enter()


if __name__ == "__main__": # pragma: no cover
import argparse
import sys

parser = argparse.ArgumentParser(description="BEC Main Application")
parser.add_argument(
"--examples", action="store_true", help="Show the Examples section with waveform demo views"
)
# Let Qt consume the remaining args
args, qt_args = parser.parse_known_args(sys.argv[1:])

app = QApplication([sys.argv[0], *qt_args])
apply_theme("dark")
w = BECMainApp(show_examples=args.examples)

screen = app.primaryScreen()
screen_geometry = screen.availableGeometry()
screen_width = screen_geometry.width()
screen_height = screen_geometry.height()
# 70% of screen height, keep 16:9 ratio
height = int(screen_height * 0.9)
width = int(height * (16 / 9))

# If width exceeds screen width, scale down
if width > screen_width * 0.9:
width = int(screen_width * 0.9)
height = int(width / (16 / 9))

w.resize(width, height)
w.show()

sys.exit(app.exec())
Empty file.
Loading
Loading