Skip to content

Commit 607689c

Browse files
committed
use composition for UI and a lot of clean up
1 parent a3c8bc1 commit 607689c

10 files changed

Lines changed: 194 additions & 238 deletions

File tree

src/compas_viewer/base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
class Base:
2+
"""
3+
Base class for all components in the viewer, provides a global access to the viewer and scene.
4+
5+
Attributes
6+
----------
7+
viewer : Viewer
8+
The viewer instance.
9+
scene : Scene
10+
The scene instance.
11+
"""
12+
213
@property
314
def viewer(self):
415
from compas_viewer.viewer import Viewer
516

617
return Viewer()
18+
19+
@property
20+
def scene(self):
21+
return self.viewer.scene

src/compas_viewer/commands.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from compas.geometry import Geometry
2323
from compas.scene import Scene
2424
from compas_viewer.components.camerasetting import CameraSettingsDialog
25-
from compas_viewer.components.objectsetting import ObjectSettingDialog
2625

2726
if TYPE_CHECKING:
2827
from compas_viewer import Viewer
@@ -490,19 +489,3 @@ def load_data():
490489

491490

492491
load_data_cmd = Command(title="Load Data", callback=lambda: print("load data"))
493-
494-
495-
# =============================================================================
496-
# =============================================================================
497-
# =============================================================================
498-
# Info
499-
# =============================================================================
500-
# =============================================================================
501-
# =============================================================================
502-
503-
504-
def obj_settings(viewer: "Viewer"):
505-
ObjectSettingDialog().exec()
506-
507-
508-
obj_settings_cmd = Command(title="Object Settings", callback=obj_settings)

src/compas_viewer/components/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,23 @@
22
from .combobox import ComboBox
33
from .combobox import ViewModeAction
44
from .camerasetting import CameraSettingsDialog
5-
from .objectsetting import ObjectSettingDialog
65
from .slider import Slider
76
from .textedit import TextEdit
87
from .treeform import Treeform
98
from .sceneform import Sceneform
9+
from .objectsetting import ObjectSetting
10+
from .component import Component
1011

1112
__all__ = [
1213
"Button",
1314
"ComboBox",
1415
"CameraSettingsDialog",
15-
"ObjectSettingDialog",
1616
"Renderer",
1717
"Slider",
1818
"TextEdit",
1919
"Treeform",
2020
"Sceneform",
21+
"ObjectSetting",
22+
"Component",
2123
"ViewModeAction",
2224
]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from PySide6.QtWidgets import QWidget
2+
3+
from compas_viewer.base import Base
4+
5+
6+
class Component(Base):
7+
def __init__(self):
8+
self.widget = QWidget()
9+
10+
def update(self):
11+
self.widget.update()
Lines changed: 95 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,167 +1,122 @@
1-
from typing import TYPE_CHECKING
2-
31
from PySide6.QtCore import Qt
4-
from PySide6.QtCore import Signal
5-
from PySide6.QtWidgets import QDialog
6-
from PySide6.QtWidgets import QPushButton
2+
from PySide6.QtWidgets import QHBoxLayout
3+
from PySide6.QtWidgets import QLabel
74
from PySide6.QtWidgets import QScrollArea
85
from PySide6.QtWidgets import QVBoxLayout
96
from PySide6.QtWidgets import QWidget
107

11-
from compas_viewer.base import Base
8+
from compas_viewer.components.color import ColorDialog
9+
from compas_viewer.components.component import Component
1210
from compas_viewer.components.double_edit import DoubleEdit
13-
from compas_viewer.components.label import LabelWidget
14-
from compas_viewer.components.layout import SettingLayout
1511
from compas_viewer.components.textedit import TextEdit
1612

17-
if TYPE_CHECKING:
18-
from compas_viewer import Viewer
19-
2013

21-
class ObjectSetting(QWidget):
14+
class ObjectSetting(Component):
2215
"""
23-
A QWidget to manage the settings of objects in the viewer.
24-
25-
Parameters
26-
----------
27-
viewer : Viewer
28-
The viewer instance containing the objects.
29-
items : list
30-
A list of dictionaries containing the settings for the object.
31-
32-
Attributes
33-
----------
34-
viewer : Viewer
35-
The viewer instance.
36-
items : list
37-
A list of dictionaries containing the settings for the object.
38-
layout : QVBoxLayout
39-
The main layout for the widget.
40-
update_button : QPushButton
41-
The button to trigger the object update.
42-
43-
Methods
44-
-------
45-
clear_layout(layout)
46-
Clears all widgets and sub-layouts from the given layout.
47-
update()
48-
Updates the layout with the latest object settings.
49-
obj_update()
50-
Applies the settings from spin boxes to the selected objects.
16+
A component to manage the settings of objects in the viewer.
5117
"""
5218

53-
update_requested = Signal()
54-
55-
def __init__(self, viewer: "Viewer", items: list[dict]):
19+
def __init__(self):
5620
super().__init__()
57-
self.viewer = viewer
58-
self.items = items
59-
self.setting_layout = SettingLayout(viewer=self.viewer, items=self.items, type="obj_setting")
60-
# Main layout
61-
self.main_layout = QVBoxLayout(self)
62-
63-
# Scroll area setup
64-
self.scroll_area = QScrollArea(self)
65-
self.scroll_area.setWidgetResizable(True)
21+
self.widget = QScrollArea()
22+
self.widget.setWidgetResizable(True)
23+
self.reset()
24+
25+
def reset(self):
26+
"""Reset the content widget and layout to a clean state."""
27+
self.sub_widgets = {}
6628
self.scroll_content = QWidget()
6729
self.scroll_layout = QVBoxLayout(self.scroll_content)
6830
self.scroll_layout.setAlignment(Qt.AlignTop)
69-
self.scroll_area.setWidget(self.scroll_content)
70-
71-
self.main_layout.addWidget(self.scroll_area)
72-
73-
def clear_layout(self, layout):
74-
"""Clear all widgets from the layout."""
75-
while layout.count():
76-
item = layout.takeAt(0)
77-
widget = item.widget()
78-
if widget is not None:
79-
widget.deleteLater()
80-
else:
81-
sub_layout = item.layout()
82-
if sub_layout is not None:
83-
self.clear_layout(sub_layout)
31+
self.widget.setWidget(self.scroll_content)
32+
self.settings_layout = QVBoxLayout()
33+
self.settings_layout.setSpacing(0)
34+
self.settings_layout.setContentsMargins(0, 0, 0, 0)
35+
self.scroll_layout.addLayout(self.settings_layout)
36+
37+
@property
38+
def selected(self):
39+
return [obj for obj in self.scene.objects if obj.is_selected]
40+
41+
def add_row(self, attr_name: str, widget: QWidget = None) -> None:
42+
"""Create a setting row with label and widget, then add it to the layout."""
43+
row_layout = QHBoxLayout()
44+
row_layout.setSpacing(0)
45+
row_layout.setContentsMargins(0, 0, 0, 0)
46+
47+
label = QLabel(attr_name)
48+
row_layout.addWidget(label)
49+
50+
if widget:
51+
row_layout.addWidget(widget)
52+
self.sub_widgets[attr_name] = widget
53+
54+
self.settings_layout.addLayout(row_layout)
55+
56+
def populate(self) -> None:
57+
"""Populate the layout with the settings of the selected object."""
58+
obj = self.selected[0]
59+
60+
if not obj:
61+
return
62+
63+
self.add_row("name", TextEdit(text=str(obj.name)))
64+
65+
if hasattr(obj, "pointcolor") and obj.pointcolor is not None:
66+
self.add_row("pointcolor", ColorDialog(obj=obj, attr="pointcolor"))
67+
68+
if hasattr(obj, "linecolor") and obj.linecolor is not None:
69+
self.add_row("linecolor", ColorDialog(obj=obj, attr="linecolor"))
70+
71+
if hasattr(obj, "facecolor") and obj.facecolor is not None:
72+
self.add_row("facecolor", ColorDialog(obj=obj, attr="facecolor"))
73+
74+
if hasattr(obj, "linewidth") and obj.linewidth is not None:
75+
self.add_row("linewidth", DoubleEdit(title=None, value=obj.linewidth, min_val=0.0, max_val=10.0))
76+
77+
if hasattr(obj, "pointsize") and obj.pointsize is not None:
78+
self.add_row("pointsize", DoubleEdit(title=None, value=obj.pointsize, min_val=0.0, max_val=10.0))
79+
80+
if hasattr(obj, "opacity") and obj.opacity is not None:
81+
self.add_row("opacity", DoubleEdit(title=None, value=obj.opacity, min_val=0.0, max_val=1.0))
8482

8583
def update(self):
8684
"""Update the layout with the latest object settings."""
87-
self.clear_layout(self.scroll_layout)
88-
self.setting_layout.generate_layout()
89-
90-
if len(self.setting_layout.widgets) != 0:
91-
self.scroll_layout.addLayout(self.setting_layout.layout)
92-
for _, widget in self.setting_layout.widgets.items():
93-
if isinstance(widget, DoubleEdit):
94-
widget.spinbox.valueChanged.connect(self.obj_update)
95-
elif isinstance(widget, TextEdit):
96-
widget.text_edit.textChanged.connect(self.obj_update)
85+
self.reset()
86+
87+
if len(self.selected) == 1:
88+
self.populate()
89+
self._add_event_listeners()
90+
elif len(self.selected) > 1:
91+
self.add_row("Multiple objects selected")
9792
else:
98-
self.scroll_layout.addWidget(LabelWidget(text="No object Selected", alignment="center"))
93+
self.add_row("No object selected")
9994

100-
def obj_update(self):
101-
"""Apply the settings from spin boxes to the selected objects."""
102-
for obj in self.viewer.scene.objects:
103-
if obj.is_selected:
104-
obj.name = self.setting_layout.widgets["Name_text_edit"].text_edit.toPlainText()
105-
obj.linewidth = self.setting_layout.widgets["Line_Width_double_edit"].spinbox.value()
106-
obj.pointsize = self.setting_layout.widgets["Point_Size_double_edit"].spinbox.value()
107-
obj.opacity = self.setting_layout.widgets["Opacity_double_edit"].spinbox.value()
108-
obj.update()
95+
def _add_event_listeners(self):
96+
"""Add event listeners to the sub widgets."""
10997

98+
def _update_obj():
99+
if len(self.selected) == 0:
100+
return
110101

111-
class ObjectSettingDialog(QDialog, Base):
112-
"""
113-
A dialog for displaying and updating object settings in Qt applications.
114-
This dialog allows users to modify object properties such as line width, point size, and opacity,
115-
and applies these changes dynamically.
116-
117-
Parameters
118-
----------
119-
items : list
120-
A list of dictionaries containing the settings for the object.
121-
122-
Attributes
123-
----------
124-
layout : QVBoxLayout
125-
The layout of the dialog.
126-
items : list
127-
A list of dictionaries containing the settings for the object.
128-
update_button : QPushButton
129-
Button to apply changes to the selected objects.
130-
131-
Methods
132-
-------
133-
update()
134-
Updates the properties of selected objects and closes the dialog.
135-
136-
Example
137-
-------
138-
>>> dialog = ObjectInfoDialog()
139-
>>> dialog.exec()
140-
"""
102+
obj = self.selected[0]
141103

142-
def __init__(self, items: list[dict]) -> None:
143-
super().__init__()
144-
self.items = items
145-
self.setWindowTitle("Object Settings")
146-
self.layout = QVBoxLayout(self)
147-
self.setting_layout = SettingLayout(viewer=self.viewer, items=self.items, type="obj_setting")
148-
149-
if self.setting_layout is not None:
150-
text = "Update Object"
151-
self.layout.addLayout(self.setting_layout.layout)
152-
else:
153-
text = "No object selected."
104+
for attr_name, widget in self.sub_widgets.items():
105+
if not hasattr(obj, attr_name):
106+
continue
107+
if isinstance(widget, TextEdit):
108+
value = widget.text_edit.toPlainText()
109+
elif isinstance(widget, DoubleEdit):
110+
value = widget.spinbox.value()
111+
else:
112+
continue
154113

155-
self.update_button = QPushButton(text, self)
156-
self.update_button.clicked.connect(self.obj_update)
157-
self.layout.addWidget(self.update_button)
114+
setattr(obj, attr_name, value)
158115

159-
def obj_update(self) -> None:
160-
for obj in self.viewer.scene.objects:
161-
if obj.is_selected:
162-
obj.linewidth = self.setting_layout.widgets["Line_Width_double_edit"].spinbox.value()
163-
obj.pointsize = self.setting_layout.widgets["Point_Size_double_edit"].spinbox.value()
164-
obj.opacity = self.setting_layout.widgets["Opacity_double_edit"].spinbox.value()
165-
obj.update()
116+
obj.update()
166117

167-
self.accept()
118+
for widget in self.sub_widgets.values():
119+
if isinstance(widget, TextEdit):
120+
widget.text_edit.textChanged.connect(_update_obj)
121+
elif isinstance(widget, DoubleEdit):
122+
widget.spinbox.valueChanged.connect(_update_obj)

0 commit comments

Comments
 (0)