@@ -21,6 +21,9 @@ class BoundComponent(Component):
2121 The name of the attribute/key to be bound.
2222 action : Callable[[Component, float], None]
2323 A function to call when the value changes. Receives the component and new value.
24+ watch_interval : int, optional
25+ Interval in milliseconds to check for changes in the bound object.
26+ If None, watching is disabled. Default is 100.
2427
2528 Attributes
2629 ----------
@@ -30,6 +33,12 @@ class BoundComponent(Component):
3033 The name of the attribute/key being bound.
3134 action : Callable[[Component, float], None]
3235 The action function to call when the value changes.
36+ watch_interval : int or None
37+ The watching interval in milliseconds, or None if watching is disabled. Default is 100.
38+ _watch_timer : Timer or None
39+ The timer used for watching changes in the bound object.
40+ _last_watched_value : Any
41+ The last known value of the bound attribute from watching.
3342
3443 Example
3544 -------
@@ -39,17 +48,28 @@ class BoundComponent(Component):
3948 >>> def my_action(component, value):
4049 ... print(f"Value changed to: {value}")
4150 >>> obj = MyObject()
51+ >>> # Component with default watcher (100ms)
4252 >>> component = BoundComponent(obj, "value", my_action)
43- >>> component.set_attr(20.0)
44- >>> print(component.get_attr()) # prints 20.0
53+ >>> # Component without watcher
54+ >>> component = BoundComponent(obj, "value", my_action, watch_interval=None)
55+ >>> # Component with custom watcher interval
56+ >>> component = BoundComponent(obj, "value", my_action, watch_interval=200)
4557 """
4658
47- def __init__ (self , obj : Union [object , dict ], attr : str , action : Callable [[Component , float ], None ]):
59+ def __init__ (self , obj : Union [object , dict ], attr : str , action : Callable [[Component , float ], None ], watch_interval : int = 100 ):
4860 super ().__init__ ()
4961
5062 self .obj = obj
5163 self .attr = attr
5264 self .action = action
65+ self .watch_interval = watch_interval
66+ self ._watch_timer = None
67+ self ._last_watched_value = None
68+ self ._updating_from_watch = False
69+
70+ # Start watching if interval is provided
71+ if self .watch_interval is not None :
72+ self .start_watching ()
5373
5474 def get_attr (self ):
5575 """
@@ -98,3 +118,86 @@ def on_value_changed(self, value: Any):
98118 self .set_attr (value )
99119 if self .action is not None :
100120 self .action (self , value )
121+
122+ def start_watching (self ):
123+ """
124+ Start watching the bound object for changes.
125+
126+ This method starts a timer that periodically checks if the bound attribute
127+ has changed and updates the component accordingly.
128+ """
129+ if self .watch_interval is None :
130+ return
131+
132+ if self ._watch_timer is not None :
133+ self .stop_watching ()
134+
135+ from compas_viewer .timer import Timer
136+
137+ self ._last_watched_value = self .get_attr ()
138+ self ._watch_timer = Timer (self .watch_interval , self ._check_for_changes )
139+
140+ def stop_watching (self ):
141+ """
142+ Stop watching the bound object for changes.
143+ """
144+ if self ._watch_timer is not None :
145+ self ._watch_timer .stop ()
146+ self ._watch_timer = None
147+
148+ def _check_for_changes (self ):
149+ """
150+ Check if the bound attribute has changed and update the component if needed.
151+
152+ This method is called periodically by the watch timer.
153+ """
154+ if self ._updating_from_watch :
155+ return
156+
157+ # Skip checking if widget is not visible to save resources
158+ if not self .widget .isVisible ():
159+ return
160+
161+ current_value = self .get_attr ()
162+ if current_value != self ._last_watched_value :
163+ self ._last_watched_value = current_value
164+ self ._updating_from_watch = True
165+ try :
166+ print ("sync from bound object" , self .obj , self .attr )
167+ self .sync_from_bound_object (current_value )
168+ finally :
169+ self ._updating_from_watch = False
170+
171+ def sync_from_bound_object (self , value : Any ):
172+ """
173+ Sync the component's display with the bound object's value.
174+
175+ This method should be overridden by subclasses to update their
176+ specific UI elements when the bound object changes.
177+
178+ Parameters
179+ ----------
180+ value : Any
181+ The new value from the bound object.
182+ """
183+ # Base implementation does nothing - subclasses should override
184+ pass
185+
186+ def set_watch_interval (self , interval : int ):
187+ """
188+ Set or change the watch interval.
189+
190+ Parameters
191+ ----------
192+ interval : int or None
193+ The new interval in milliseconds, or None to disable watching.
194+ """
195+ was_watching = self ._watch_timer is not None
196+
197+ if was_watching :
198+ self .stop_watching ()
199+
200+ self .watch_interval = interval
201+
202+ if interval is not None :
203+ self .start_watching ()
0 commit comments