diff --git a/lumen/filters/base.py b/lumen/filters/base.py index c6c51f675..1fed6f846 100644 --- a/lumen/filters/base.py +++ b/lumen/filters/base.py @@ -176,6 +176,11 @@ class BaseWidgetFilter(Filter): disabled = param.Boolean(default=False, doc=""" Whether the filter should be disabled.""") + throttled = param.Boolean(default=True, doc=""" + If the widget has a value_throttled parameter use that instead, + ensuring that no intermediate events are generated, e.g. when + dragging a slider.""") + visible = param.Boolean(default=True, doc=""" Whether the filter should be visible.""") @@ -193,7 +198,12 @@ def _url_sync_error(self, values): @property def panel(self): widget = self.widget.clone() - self.widget.link(widget, value='value', visible='visible', disabled='disabled', bidirectional=True) + if self.throttled and 'value_throttled' in self.widget.param: + widget.link(self.widget, value_throttled='value') + self.widget.link(widget, value='value') + self.widget.link(widget, visible='visible', disabled='disabled', bidirectional=True) + else: + self.widget.link(widget, value='value', visible='visible', disabled='disabled', bidirectional=True) return widget @@ -228,7 +238,7 @@ def __init__(self, **params): self.widget.name = self.label self.widget.visible = self.visible self.widget.disabled = self.disabled - self.widget.link(self, value='value', visible='visible', disabled='disabled', bidirectional=True) + self.widget.link(self, bidirectional=True, value='value', visible='visible', disabled='disabled') if self.default is not None: self.widget.value = self.default diff --git a/lumen/tests/filters/test_base.py b/lumen/tests/filters/test_base.py index 9fc07dbd9..c5ac7dddb 100644 --- a/lumen/tests/filters/test_base.py +++ b/lumen/tests/filters/test_base.py @@ -1,3 +1,5 @@ +import param + from lumen.filters import Filter @@ -5,9 +7,9 @@ def test_resolve_module_type(): assert Filter._get_type('lumen.filters.base.Filter') is Filter -def test_widget_filter_link(): +def test_widget_filter_link_unthrottled(): wfilter = Filter.from_spec( - {'type': 'widget', 'field': 'test'}, + {'type': 'widget', 'field': 'test', 'throttled': False}, {'example': { 'test': { 'type': 'integer', @@ -19,7 +21,7 @@ def test_widget_filter_link(): widget = wfilter.panel assert widget.value == (0, 2) - + widget.value = (1, 2) assert wfilter.value == (1, 2) @@ -35,3 +37,28 @@ def test_widget_filter_link(): widget.disabled = False assert widget.disabled == False + + +def test_widget_filter_link_throttled(): + wfilter = Filter.from_spec( + {'type': 'widget', 'field': 'test'}, + {'example': { + 'test': { + 'type': 'integer', + 'inclusiveMinimum': 0, + 'inclusiveMaximum': 2 + } + }} + ) + widget = wfilter.panel + + assert widget.value == (0, 2) + + with param.edit_constant(widget): + widget.value_throttled = (1, 2) + + assert wfilter.value == (1, 2) + + wfilter.value = (2, 2) + + assert widget.value == (2, 2) diff --git a/lumen/tests/test_variables.py b/lumen/tests/test_variables.py index 562e872ba..695346579 100644 --- a/lumen/tests/test_variables.py +++ b/lumen/tests/test_variables.py @@ -1,5 +1,7 @@ import os +import param + from panel.widgets import IntSlider from lumen.variables import Variables, Variable @@ -47,11 +49,23 @@ def test_resolve_widget_variable_by_module_ref(): assert isinstance(var._widget, IntSlider) -def test_widget_variable_linking(): - var = Variable.from_spec({'type': 'widget', 'kind': 'IntSlider'}) +def test_widget_variable_linking_unthrottled(): + var = Variable.from_spec({'type': 'widget', 'kind': 'IntSlider', 'throttled': False}) var._widget.value = 3 assert var.value == 3 var.value = 4 assert var._widget.value == 4 + + +def test_widget_variable_linking_throttled(): + var = Variable.from_spec({'type': 'widget', 'kind': 'IntSlider'}) + + with param.edit_constant(var._widget): + var._widget.value_throttled = 3 + + assert var.value == 3 + + var.value = 4 + assert var._widget.value == 4 diff --git a/lumen/variables/base.py b/lumen/variables/base.py index 91ddb7518..93ad34f8a 100644 --- a/lumen/variables/base.py +++ b/lumen/variables/base.py @@ -178,12 +178,18 @@ class Widget(Variable): A Widget variable that updates when the widget value changes. """ + throttled = param.Boolean(default=True, doc=""" + If the widget has a value_throttled parameter use that instead, + ensuring that no intermediate events are generated, e.g. when + dragging a slider.""") + variable_type = 'widget' def __init__(self, **params): default = params.pop('default', None) refs = params.pop('refs', {}) - super().__init__(default=default, refs=refs, name=params.get('name')) + throttled = params.pop('throttled', True) + super().__init__(default=default, refs=refs, name=params.get('name'), throttled=throttled) kind = params.pop('kind', None) if kind is None: raise ValueError("A Widget Variable type must declare the kind of widget.") @@ -202,7 +208,11 @@ def __init__(self, **params): pass deserialized[k] = v self._widget = widget_type(**deserialized) - self._widget.link(self, value='value', bidirectional=True) + if self.throttled and 'value_throttled' in self._widget.param: + self._widget.link(self, value_throttled='value') + self.param.watch(lambda e: self._widget.param.update({'value': e.new}), 'value') + else: + self._widget.link(self, value='value', bidirectional=True) @property def panel(self):