Skip to content

Commit 3e109f0

Browse files
committed
restructure text edit and number edit
1 parent 404fb4b commit 3e109f0

7 files changed

Lines changed: 290 additions & 131 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from typing import Callable
2+
from typing import Union
3+
from typing import Any
4+
from .component import Component
5+
6+
7+
class BoundComponent(Component):
8+
"""
9+
Base class for components that are bound to object attributes.
10+
11+
This class provides common functionality for UI components that need to be bound
12+
to an attribute of an object or dictionary. It handles getting and setting values
13+
from the bound attribute and provides a callback mechanism for when values change.
14+
15+
Parameters
16+
----------
17+
obj : Union[object, dict]
18+
The object or dictionary containing the attribute to be bound.
19+
attr : str
20+
The name of the attribute/key to be bound.
21+
callback : Callable[[Component, float], None]
22+
A function to call when the value changes. Receives the component and new value.
23+
24+
Attributes
25+
----------
26+
obj : Union[object, dict]
27+
The object or dictionary containing the attribute being bound.
28+
attr : str
29+
The name of the attribute/key being bound.
30+
callback : Callable[[Component, float], None]
31+
The callback function to call when the value changes.
32+
33+
Example
34+
-------
35+
>>> class MyObject:
36+
... def __init__(self):
37+
... self.value = 10.0
38+
>>> def my_callback(component, value):
39+
... print(f"Value changed to: {value}")
40+
>>> obj = MyObject()
41+
>>> component = BoundComponent(obj, "value", my_callback)
42+
>>> component.set_attr(20.0)
43+
>>> print(component.get_attr()) # prints 20.0
44+
"""
45+
46+
def __init__(self, obj: Union[object, dict], attr: str, callback: Callable[[Component, float], None]):
47+
super().__init__()
48+
49+
self.obj = obj
50+
self.attr = attr
51+
self.callback = callback
52+
53+
def get_attr(self):
54+
"""
55+
Get the current value of the bound attribute.
56+
57+
Returns
58+
-------
59+
float
60+
The current value of the attribute.
61+
"""
62+
if isinstance(self.obj, dict):
63+
return self.obj[self.attr]
64+
else:
65+
return getattr(self.obj, self.attr)
66+
67+
def set_attr(self, value: float):
68+
"""
69+
Set the value of the bound attribute.
70+
71+
Parameters
72+
----------
73+
value : float
74+
The new value to set.
75+
"""
76+
if isinstance(self.obj, dict):
77+
self.obj[self.attr] = value
78+
else:
79+
setattr(self.obj, self.attr, value)
80+
81+
def on_value_changed(self, value: Any):
82+
"""
83+
Handle value changes for the bound attribute.
84+
85+
This method is called when the component's value changes. It updates the bound
86+
attribute and calls the callback function if one was provided.
87+
88+
Parameters
89+
----------
90+
value : float
91+
The new value to set.
92+
"""
93+
self.set_attr(value)
94+
if self.callback is not None:
95+
self.callback(self, value)

src/compas_viewer/components/double_edit.py

Lines changed: 0 additions & 69 deletions
This file was deleted.

src/compas_viewer/components/layout.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from compas_viewer.components.color import ColorComboBox
1212
from compas_viewer.components.color import ColorDialog
13-
from compas_viewer.components.double_edit import DoubleEdit
13+
from compas_viewer.components.numberedit import NumberEdit
1414
from compas_viewer.components.label import LabelWidget
1515
from compas_viewer.components.textedit import TextEdit
1616

@@ -144,7 +144,7 @@ def set_layout(self, items: list[dict], obj: Any) -> None:
144144

145145
if type == "double_edit":
146146
value = action(obj)
147-
widget = DoubleEdit(title=sub_title, value=value, min_val=min_val, max_val=max_val)
147+
widget = NumberEdit(title=sub_title, value=value, min_val=min_val, max_val=max_val)
148148
elif type == "label":
149149
text = action(obj)
150150
widget = LabelWidget(text=text, alignment="center")
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from PySide6.QtWidgets import QWidget
2+
from PySide6.QtWidgets import QHBoxLayout
3+
from PySide6.QtWidgets import QLabel
4+
from PySide6.QtWidgets import QDoubleSpinBox
5+
from typing import Callable
6+
from typing import Union
7+
from .component import Component
8+
from .boundcomponent import BoundComponent
9+
10+
11+
class NumberEdit(BoundComponent):
12+
"""
13+
This component creates a labeled number spin box that can be bound to an object's attribute
14+
(either a dictionary key or object attribute). When the value changes, it automatically
15+
updates the bound attribute and optionally calls a callback function.
16+
17+
Parameters
18+
----------
19+
obj : Union[object, dict]
20+
The object or dictionary containing the attribute to be edited.
21+
attr : str
22+
The name of the attribute/key to be edited.
23+
title : str, optional
24+
The label text to be displayed next to the spin box. If None, uses the attr name.
25+
min_val : float, optional
26+
The minimum value allowed in the spin box. If None, uses the default minimum.
27+
max_val : float, optional
28+
The maximum value allowed in the spin box. If None, uses the default maximum.
29+
step : float, optional
30+
The step size for the spin box. Defaults to 0.1.
31+
decimals : int, optional
32+
The number of decimal places to display. Defaults to 1.
33+
callback : Callable[[Component, float], None], optional
34+
A function to call when the value changes. Receives the component and new value.
35+
36+
Attributes
37+
----------
38+
obj : Union[object, dict]
39+
The object or dictionary containing the attribute being edited.
40+
attr : str
41+
The name of the attribute/key being edited.
42+
callback : Callable[[Component, float], None] or None
43+
The callback function to call when the value changes.
44+
widget : QWidget
45+
The main widget containing the layout.
46+
layout : QHBoxLayout
47+
The horizontal layout containing the label and the spin box.
48+
label : QLabel
49+
The label displaying the title.
50+
spinbox : QDoubleSpinBox
51+
The double spin box for editing the floating-point number.
52+
53+
Example
54+
-------
55+
>>> class MyObject:
56+
... def __init__(self):
57+
... self.x = 5.0
58+
>>> obj = MyObject()
59+
>>> component = NumberEdit(obj, "x", title="X Coordinate", min_val=0.0, max_val=10.0)
60+
"""
61+
62+
def __init__(
63+
self,
64+
obj: Union[object, dict],
65+
attr: str,
66+
title: str = None,
67+
min_val: float = None,
68+
max_val: float = None,
69+
step: float = 0.1,
70+
decimals: int = 1,
71+
callback: Callable[[Component, float], None] = None,
72+
):
73+
super().__init__(obj, attr, callback=callback)
74+
75+
self.widget = QWidget()
76+
self.layout = QHBoxLayout()
77+
78+
title = title if title is not None else attr
79+
self.label = QLabel(title)
80+
self.spinbox = QDoubleSpinBox()
81+
self.spinbox.setDecimals(decimals)
82+
self.spinbox.setSingleStep(step)
83+
self.spinbox.setMaximumSize(85, 25)
84+
85+
self.spinbox.setValue(self.get_attr())
86+
87+
if min_val is not None:
88+
self.spinbox.setMinimum(min_val)
89+
if max_val is not None:
90+
self.spinbox.setMaximum(max_val)
91+
92+
self.layout.addWidget(self.label)
93+
self.layout.addWidget(self.spinbox)
94+
self.widget.setLayout(self.layout)
95+
self.spinbox.valueChanged.connect(self.on_value_changed)

0 commit comments

Comments
 (0)