diff --git a/CHANGELOG.md b/CHANGELOG.md index 21f8e79768..2088bfda98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added - +* Added `QTabWidget` to `SideBarRight`. ### Changed ### Removed diff --git a/scripts/tablayout.py b/scripts/tablayout.py new file mode 100644 index 0000000000..f350297fec --- /dev/null +++ b/scripts/tablayout.py @@ -0,0 +1,25 @@ +from compas.colors import Color +from compas.geometry import Box +from compas.geometry import Frame +from compas_viewer.viewer import Viewer +from compas_viewer.config import Config + +config = Config() +for item in config.ui.sidebar.items: + if item['type'] == 'Sceneform': + item['area'] = 'tab' +viewer = Viewer(config) + +N = 10 +M = 10 + +for i in range(N): + for j in range(M): + viewer.scene.add( + Box(0.5, 0.5, 0.5, Frame([i, j, 0], [1, 0, 0], [0, 1, 0])), + linecolor=Color.white(), + facecolor=Color(i / N, j / M, 0.0), + name=f"Box_{i}_{j}", + ) + +viewer.show() diff --git a/src/compas_viewer/components/objectsetting.py b/src/compas_viewer/components/objectsetting.py index 2a536befb3..9addd5d19f 100644 --- a/src/compas_viewer/components/objectsetting.py +++ b/src/compas_viewer/components/objectsetting.py @@ -1,5 +1,3 @@ -from typing import TYPE_CHECKING - from PySide6.QtCore import Qt from PySide6.QtCore import Signal from PySide6.QtWidgets import QDialog @@ -14,9 +12,6 @@ from compas_viewer.components.layout import SettingLayout from compas_viewer.components.textedit import TextEdit -if TYPE_CHECKING: - from compas_viewer import Viewer - class ObjectSetting(QWidget): """ @@ -52,11 +47,9 @@ class ObjectSetting(QWidget): update_requested = Signal() - def __init__(self, viewer: "Viewer", items: list[dict]): + def __init__(self, items: list[dict]): super().__init__() - self.viewer = viewer self.items = items - self.setting_layout = SettingLayout(viewer=self.viewer, items=self.items, type="obj_setting") # Main layout self.main_layout = QVBoxLayout(self) @@ -70,6 +63,12 @@ def __init__(self, viewer: "Viewer", items: list[dict]): self.main_layout.addWidget(self.scroll_area) + @property + def viewer(self): + from compas_viewer import Viewer + + return Viewer() + def clear_layout(self, layout): """Clear all widgets from the layout.""" while layout.count(): @@ -85,6 +84,7 @@ def clear_layout(self, layout): def update(self): """Update the layout with the latest object settings.""" self.clear_layout(self.scroll_layout) + self.setting_layout = SettingLayout(viewer=self.viewer, items=self.items, type="obj_setting") self.setting_layout.generate_layout() if len(self.setting_layout.widgets) != 0: diff --git a/src/compas_viewer/components/sceneform.py b/src/compas_viewer/components/sceneform.py index 76f39c0356..e259a29024 100644 --- a/src/compas_viewer/components/sceneform.py +++ b/src/compas_viewer/components/sceneform.py @@ -36,16 +36,16 @@ class Sceneform(QTreeWidget): def __init__( self, - columns: list[dict], + items: list[dict], column_editable: Optional[list[bool]] = None, show_headers: bool = True, callback: Optional[Callable] = None, ): super().__init__() - self.columns = columns + self.columns = items self.checkbox_columns: dict[int, str] = {} - self.column_editable = (column_editable or [False]) + [False] * (len(columns) - len(column_editable or [False])) - self.setColumnCount(len(columns)) + self.column_editable = (column_editable or [False]) + [False] * (len(self.columns) - len(column_editable or [False])) + self.setColumnCount(len(self.columns)) self.setHeaderLabels(col["title"] for col in self.columns) self.setHeaderHidden(not show_headers) self.setSelectionMode(QTreeWidget.SingleSelection) diff --git a/src/compas_viewer/config.py b/src/compas_viewer/config.py index bf3571618f..ed117c0c45 100644 --- a/src/compas_viewer/config.py +++ b/src/compas_viewer/config.py @@ -250,13 +250,15 @@ class SidebarConfig(ConfigBase): items: list[dict] = field( default_factory=lambda: [ { + "area": "splitter", "type": "Sceneform", - "columns": [ + "items": [ {"title": "Name", "type": "label", "text": lambda obj: obj.name}, {"title": "Show", "type": "checkbox", "checked": lambda obj: obj.show, "action": lambda obj, checked: setattr(obj, "show", checked)}, ], }, { + "area": "tab", "type": "ObjectSetting", "items": [ {"title": "Name", "items": [{"type": "text_edit", "action": lambda obj: obj.name}]}, diff --git a/src/compas_viewer/ui/sidebar.py b/src/compas_viewer/ui/sidebar.py index 0fe5b52f88..9da4520ddc 100644 --- a/src/compas_viewer/ui/sidebar.py +++ b/src/compas_viewer/ui/sidebar.py @@ -1,8 +1,11 @@ from typing import TYPE_CHECKING from typing import Callable +from typing import Optional from PySide6 import QtCore from PySide6.QtWidgets import QSplitter +from PySide6.QtWidgets import QTabWidget +from PySide6.QtWidgets import QWidget from compas_viewer.components import Sceneform from compas_viewer.components.objectsetting import ObjectSetting @@ -10,44 +13,30 @@ if TYPE_CHECKING: from .ui import UI +type_registry = { + "Sceneform": Sceneform, + "ObjectSetting": ObjectSetting, +} + class SideBarRight: def __init__(self, ui: "UI", show: bool, items: list[dict[str, Callable]]) -> None: self.ui = ui self.widget = QSplitter(QtCore.Qt.Orientation.Vertical) self.widget.setChildrenCollapsible(True) + self._tab_widget: Optional[QTabWidget] = None self.show = show self.hide_widget = True self.items = items - def add_items(self) -> None: - if not self.items: - return - - for item in self.items: - itemtype = item.get("type", None) - - if itemtype == "Sceneform": - columns = item.get("columns", None) - if columns is None: - raise ValueError("Please setup config for Sceneform") - self.sceneform = Sceneform(columns=columns) - self.widget.addWidget(self.sceneform) - - elif itemtype == "ObjectSetting": - items = item.get("items", None) - if items is None: - raise ValueError("Please setup config for ObjectSetting") - self.object_setting = ObjectSetting(viewer=self.ui.viewer, items=items) - self.widget.addWidget(self.object_setting) - - self.show_sceneform = True - self.show_objectsetting = True + # add widgets manualy to avoide multiple emits signals from QTabWidget + self.update_widgets = [] - def update(self): - self.widget.update() - for widget in self.widget.children(): - widget.update() + @property + def tab_widget(self): + if self._tab_widget is None: + self._tab_widget = QTabWidget(self.widget) + return self._tab_widget @property def show(self): @@ -59,16 +48,42 @@ def show(self, value: bool): @property def show_sceneform(self): - return self.sceneform.isVisible() + return getattr(self, "Sceneform", QWidget()).isVisible() @show_sceneform.setter def show_sceneform(self, value: bool): - self.sceneform.setVisible(value) + getattr(self, "Sceneform", QWidget()).setVisible(value) @property def show_objectsetting(self): - return self.object_setting.isVisible() + return getattr(self, "ObjectSetting", QWidget()).isVisible() @show_objectsetting.setter def show_objectsetting(self, value: bool): - self.object_setting.setVisible(value) + getattr(self, "ObjectSetting", QWidget()).setVisible(value) + + def add_items(self) -> None: + if not self.items: + return + + for item in self.items: + area = item.get("area", None) + itemtype = item.get("type", None) + items = item.get("items", None) + + if itemtype in type_registry: + if items is None: + raise ValueError(f"Please setup config for {itemtype} widget") + widget = type_registry[itemtype](items=items) + # set the attribute dynamically + setattr(self, itemtype, widget) + if area == "tab": + self.tab_widget.addTab(widget, itemtype) + elif area == "splitter": + self.widget.addWidget(widget) + self.update_widgets.append(widget) + + def update(self): + self.widget.update() + for widget in self.update_widgets: + widget.update()