From e4f0d473218b470ffafe0acb152f46dcd3a08098 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 13 Aug 2022 23:01:49 +0200 Subject: [PATCH] Implement Player value_throttled --- .../reference/widgets/DiscretePlayer.ipynb | 5 +- examples/reference/widgets/Player.ipynb | 7 +-- panel/models/player.ts | 16 +++++-- panel/models/widgets.py | 2 + panel/widgets/player.py | 46 +++++++++++++------ 5 files changed, 54 insertions(+), 22 deletions(-) diff --git a/examples/reference/widgets/DiscretePlayer.ipynb b/examples/reference/widgets/DiscretePlayer.ipynb index 5f13d401f3..247ff2493d 100644 --- a/examples/reference/widgets/DiscretePlayer.ipynb +++ b/examples/reference/widgets/DiscretePlayer.ipynb @@ -27,15 +27,16 @@ "\n", "* **``direction``** (int): Current play direction of the Player (-1: playing in reverse,\n", "0: paused, 1: playing).\n", + "* **``interval``** (int): Interval in milliseconds between updates\n", + "* **``loop_policy``** (str): Looping policy; must be one of 'once', 'loop', or 'reflect'\n", "* **``options``** (list or dict): A list or dictionary of options to select from\n", "* **``value``** (object): The current value; must be one of the option values\n", + "* **``value_throttled``** (object): The current value throttled until mouseup (when selected using slider)\n", "\n", "##### Display\n", "\n", "* **``disabled``** (boolean): Whether the widget is editable\n", - "* **``interval``** (int): Interval in milliseconds between updates\n", "* **``name``** (str): The title of the widget\n", - "* **``loop_policy``** (str): Looping policy; must be one of 'once', 'loop', or 'reflect'\n", "* **``show_loop_controls``** (boolean): Whether radio buttons allowing to switch between loop policies options are shown\n", "\n", "___" diff --git a/examples/reference/widgets/Player.ipynb b/examples/reference/widgets/Player.ipynb index 72d1c8f471..20cb23e766 100644 --- a/examples/reference/widgets/Player.ipynb +++ b/examples/reference/widgets/Player.ipynb @@ -27,17 +27,18 @@ "\n", "* **``direction``** (int): Current play direction of the Player (-1: playing in reverse,\n", "0: paused, 1: playing).\n", + "* **``interval``** (int): Interval in milliseconds between updates\n", + "* **``loop_policy``** (str): Looping policy; must be one of 'once', 'loop', or 'reflect'\n", "* **``start``** (int): The range's lower bound\n", "* **``end``** (int): The range's upper bound\n", "* **``step``** (int): The interval between values\n", - "* **``value``** (object): The current value; must be one of the option values\n", + "* **``value``** (int): The current integer value\n", + "* **``value_throttled``** (int): The current integer value throttled until mouseup (when selected using slider)\n", "\n", "##### Display\n", "\n", "* **``disabled``** (boolean): Whether the widget is editable\n", - "* **``interval``** (int): Interval in milliseconds between updates\n", "* **``name``** (str): The title of the widget\n", - "* **``loop_policy``** (str): Looping policy; must be one of 'once', 'loop', or 'reflect'\n", "* **``show_loop_controls``** (boolean): Whether radio buttons allowing to switch between loop policies options are shown\n", "\n", "___" diff --git a/panel/models/player.ts b/panel/models/player.ts index f7194d98cc..9443c7217c 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -81,7 +81,12 @@ export class PlayerView extends WidgetView { this.sliderEl.value = String(this.model.value) this.sliderEl.min = String(this.model.start) this.sliderEl.max = String(this.model.end) - this.sliderEl.onchange = (ev) => this.set_frame(parseInt((ev.target as HTMLInputElement).value)) + this.sliderEl.addEventListener('input', (ev) => { + this.set_frame(parseInt((ev.target as HTMLInputElement).value), false) + }) + this.sliderEl.addEventListener('change', (ev) => { + this.set_frame(parseInt((ev.target as HTMLInputElement).value)) + }) // Buttons const button_div = div() as any @@ -211,9 +216,10 @@ export class PlayerView extends WidgetView { this.el.appendChild(this.groupEl) } - set_frame(frame: number): void { - if (this.model.value != frame) - this.model.value = frame; + set_frame(frame: number, throttled: boolean=true): void { + this.model.value = frame + if (throttled) + this.model.value_throttled = frame if (this.sliderEl.value != String(frame)) this.sliderEl.value = String(frame); } @@ -358,6 +364,7 @@ export namespace Player { step: p.Property loop_policy: p.Property value: p.Property + value_throttled: p.Property show_loop_controls: p.Property } } @@ -386,6 +393,7 @@ export class Player extends Widget { step: [ Int, 1 ], loop_policy: [ LoopPolicy, "once" ], value: [ Int, 0 ], + value_throttled: [ Int, 0 ], show_loop_controls: [ Boolean, true ], })) diff --git a/panel/models/widgets.py b/panel/models/widgets.py index 852c61efa3..0b5b37ec40 100644 --- a/panel/models/widgets.py +++ b/panel/models/widgets.py @@ -21,6 +21,8 @@ class Player(Widget): value = Int(0, help="Current value of the player app") + value_throttled = Int(0, help="Current throttled value of the player app") + step = Int(1, help="Number of steps to advance the player by.") interval = Int(500, help="Interval between updates") diff --git a/panel/widgets/player.py b/panel/widgets/player.py index 6e85ff1a2a..082c3c5d90 100644 --- a/panel/widgets/player.py +++ b/panel/widgets/player.py @@ -9,6 +9,7 @@ import param +from ..config import config from ..models.widgets import Player as _BkPlayer from ..util import indexOf, isIn from .base import Widget @@ -20,23 +21,23 @@ class PlayerBase(Widget): + direction = param.Integer(0, doc=""" + Current play direction of the Player (-1: playing in reverse, + 0: paused, 1: playing)""") + interval = param.Integer(default=500, doc=""" Interval between updates, in milliseconds. Default is 500, i.e. two updates per second.""") - loop_policy = param.ObjectSelector(default='once', - objects=['once', 'loop', 'reflect'], doc=""" + loop_policy = param.ObjectSelector( + default='once', objects=['once', 'loop', 'reflect'], doc=""" Policy used when player hits last frame""") - step = param.Integer(default=1, doc=""" - Number of frames to step forward and back by on each event.""") - show_loop_controls = param.Boolean(default=True, doc=""" Whether the loop controls radio buttons are shown""") - direction = param.Integer(0, doc=""" - Current play direction of the Player (-1: playing in reverse, - 0: paused, 1: playing)""") + step = param.Integer(default=1, doc=""" + Number of frames to step forward and back by on each event.""") height = param.Integer(default=80) @@ -48,6 +49,11 @@ class PlayerBase(Widget): __abstract = True + def __init__(self, **params): + if 'value' in params and 'value_throttled' in self.param: + params['value_throttled'] = params['value'] + super().__init__(**params) + def play(self): self.direction = 1 @@ -79,6 +85,9 @@ class Player(PlayerBase): value = param.Integer(default=0, doc="Current player value") + value_throttled = param.Integer(default=0, constant=True, doc=""" + Current throttled player value.""") + _supports_embed: ClassVar[bool] = True def __init__(self, **params): @@ -91,6 +100,14 @@ def __init__(self, **params): params['value'] = params['start'] super().__init__(**params) + def _process_property_change(self, msg): + if config.throttled: + if "value" in msg: + del msg["value"] + if "value_throttled" in msg: + msg["value"] = msg["value_throttled"] + return super()._process_property_change(msg) + def _get_embed_state(self, root, values=None, max_opts=3): if values is None: values = list(range(self.start, self.end, self.step)) @@ -121,9 +138,11 @@ class DiscretePlayer(PlayerBase, SelectBase): value = param.Parameter(doc="Current player value") + value_throttled = param.Parameter(constant=True, doc="Current player value") + _rename: ClassVar[Mapping[str, str | None]] = {'name': None, 'options': None} - _source_transforms: ClassVar[Mapping[str, str | None]] = {'value': None} + _source_transforms: ClassVar[Mapping[str, str | None]] = {'value': None, 'value_throttled': None} def _process_param_change(self, msg): values = self.values @@ -141,8 +160,9 @@ def _process_param_change(self, msg): return super()._process_param_change(msg) def _process_property_change(self, msg): - if 'value' in msg: - value = msg.pop('value') - if value < len(self.options): - msg['value'] = self.values[value] + for prop in ('value', 'value_throttled'): + if prop in msg: + value = msg.pop(prop) + if value < len(self.options): + msg[prop] = self.values[value] return msg