diff --git a/ipywidgets/widgets/widget_int.py b/ipywidgets/widgets/widget_int.py index 647ea358b8..925d2a20cf 100644 --- a/ipywidgets/widgets/widget_int.py +++ b/ipywidgets/widgets/widget_int.py @@ -224,17 +224,18 @@ def _validate_value(self, proposal): class Play(_BoundedInt): """Play/repeat buttons to step through values automatically, and optionally loop. """ - interval = CInt(100, help="The time between two animation steps (ms).").tag(sync=True) - step = CInt(1, help="Increment step").tag(sync=True) - disabled = Bool(False, help="Enable or disable user changes").tag(sync=True) - _view_name = Unicode('PlayView').tag(sync=True) _model_name = Unicode('PlayModel').tag(sync=True) - _playing = Bool(help="Whether the control is currently playing.").tag(sync=True) - _repeat = Bool(help="Whether the control will repeat in a continous loop.").tag(sync=True) + playing = Bool(help="Whether the control is currently playing.").tag(sync=True) + repeat = Bool(help="Whether the control will repeat in a continous loop.").tag(sync=True) + + interval = CInt(100, help="The time between two animation steps (ms).").tag(sync=True) + step = CInt(1, help="Increment step").tag(sync=True) + disabled = Bool(False, help="Enable or disable user changes").tag(sync=True) show_repeat = Bool(True, help="Show the repeat toggle button in the widget.").tag(sync=True) + class _BoundedIntRange(_IntRange): max = CInt(100, help="Max value").tag(sync=True) min = CInt(0, help="Min value").tag(sync=True) diff --git a/packages/controls/src/widget_int.ts b/packages/controls/src/widget_int.ts index 7f7e31ad1f..fdf5b04412 100644 --- a/packages/controls/src/widget_int.ts +++ b/packages/controls/src/widget_int.ts @@ -776,8 +776,8 @@ class PlayModel extends BoundedIntModel { return _.extend(super.defaults(), { _model_name: 'PlayModel', _view_name: 'PlayView', - _playing: false, - _repeat: false, + repeat: false, + playing: false, show_repeat: true, interval: 100, step: 1, @@ -789,42 +789,47 @@ class PlayModel extends BoundedIntModel { } loop(): void { - if (this.get('_playing')) { - const next_value = this.get('value') + this.get('step'); - if (next_value <= this.get('max')) { - this.set('value', next_value); + if (!this.get('playing')) { + return; + } + const next_value = this.get('value') + this.get('step'); + if (next_value <= this.get('max')) { + this.set('value', next_value); + this.schedule_next(); + } else { + if (this.get('repeat')) { + this.set('value', this.get('min')); this.schedule_next(); } else { - if(this.get('_repeat')) { - this.set('value', this.get('min')); - this.schedule_next(); - } else { - this.set('_playing', false); - } + this.pause(); } - this.save_changes(); } + this.save_changes(); } schedule_next(): void { - window.setTimeout(this.loop.bind(this), this.get('interval')); + this._timerId = window.setTimeout(this.loop.bind(this), this.get('interval')); } stop(): void { - this.set('_playing', false); + this.pause(); this.set('value', this.get('min')); this.save_changes(); } pause(): void { - this.set('_playing', false); + window.clearTimeout(this._timerId); + this._timerId = null; + this.set('playing', false); this.save_changes(); } - play(): void { - this.set('_playing', true); - if (this.get('value') == this.get('max')) { - // if the value is at the end, reset if first, and then schedule the next + animate(): void { + if (this._timerId !== null) { + return; + } + if (this.get('value') === this.get('max')) { + // if the value is at the end, reset it first, and then schedule the next this.set('value', this.get('min')); this.schedule_next(); this.save_changes(); @@ -833,12 +838,20 @@ class PlayModel extends BoundedIntModel { // loop will call save_changes in this case this.loop(); } + this.save_changes(); + } + + play(): void { + this.set('playing', !this.get('playing')); + this.save_changes(); } repeat(): void { - this.set('_repeat', !this.get('_repeat')); + this.set('repeat', !this.get('repeat')); this.save_changes(); } + + private _timerId: number | null = null; } export @@ -882,11 +895,11 @@ class PlayView extends DOMWidgetView { this.stopButton.onclick = this.model.stop.bind(this.model); this.repeatButton.onclick = this.model.repeat.bind(this.model); - this.listenTo(this.model, 'change:_playing', this.update_playing); - this.listenTo(this.model, 'change:_repeat', this.update_repeat); - this.listenTo(this.model, 'change:show_repeat', this.update_repeat); - this.update_playing(); - this.update_repeat(); + this.listenTo(this.model, 'change:playing', this.onPlayingChanged); + this.listenTo(this.model, 'change:repeat', this.updateRepeat); + this.listenTo(this.model, 'change:show_repeat', this.updateRepeat); + this.updatePlaying(); + this.updateRepeat(); this.update(); } @@ -896,11 +909,22 @@ class PlayView extends DOMWidgetView { this.pauseButton.disabled = disabled; this.stopButton.disabled = disabled; this.repeatButton.disabled = disabled; - this.update_playing(); + this.updatePlaying(); + } + + onPlayingChanged() { + this.updatePlaying(); + const previous = this.model.previous('playing'); + const current = this.model.get('playing'); + if (!previous && current) { + this.model.animate(); + } else { + this.model.pause(); + } } - update_playing(): void { - const playing = this.model.get('_playing'); + updatePlaying(): void { + const playing = this.model.get('playing'); const disabled = this.model.get('disabled'); if (playing) { if (!disabled) { @@ -915,8 +939,8 @@ class PlayView extends DOMWidgetView { } } - update_repeat(): void { - const repeat = this.model.get('_repeat'); + updateRepeat(): void { + const repeat = this.model.get('repeat'); this.repeatButton.style.display = this.model.get('show_repeat') ? this.playButton.style.display : 'none'; if (repeat) { this.repeatButton.classList.add('mod-active'); diff --git a/packages/schema/jupyterwidgetmodels.latest.md b/packages/schema/jupyterwidgetmodels.latest.md index f7a210bafb..46d1cc31f9 100644 --- a/packages/schema/jupyterwidgetmodels.latest.md +++ b/packages/schema/jupyterwidgetmodels.latest.md @@ -784,8 +784,6 @@ Attribute | Type | Default | Help `_model_module` | string | `'@jupyter-widgets/controls'` | `_model_module_version` | string | `'1.5.0'` | `_model_name` | string | `'PlayModel'` | -`_playing` | boolean | `false` | Whether the control is currently playing. -`_repeat` | boolean | `false` | Whether the control will repeat in a continous loop. `_view_module` | string | `'@jupyter-widgets/controls'` | `_view_module_version` | string | `'1.5.0'` | `_view_name` | string | `'PlayView'` | @@ -795,6 +793,8 @@ Attribute | Type | Default | Help `layout` | reference to Layout widget | reference to new instance | `max` | number (integer) | `100` | Max value `min` | number (integer) | `0` | Min value +`playing` | boolean | `false` | Whether the control is currently playing. +`repeat` | boolean | `false` | Whether the control will repeat in a continous loop. `show_repeat` | boolean | `true` | Show the repeat toggle button in the widget. `step` | number (integer) | `1` | Increment step `style` | reference to DescriptionStyle widget | reference to new instance | Styling customizations diff --git a/packages/schema/jupyterwidgetmodels.v8.md b/packages/schema/jupyterwidgetmodels.v8.md index 12f44ebaf4..b2ff3539ae 100644 --- a/packages/schema/jupyterwidgetmodels.v8.md +++ b/packages/schema/jupyterwidgetmodels.v8.md @@ -717,8 +717,8 @@ Attribute | Type | Default | Help `_model_module` | string | `'@jupyter-widgets/controls'` | `_model_module_version` | string | `'1.4.0'` | `_model_name` | string | `'PlayModel'` | -`_playing` | boolean | `false` | Whether the control is currently playing. -`_repeat` | boolean | `false` | Whether the control will repeat in a continous loop. +`playing` | boolean | `false` | Whether the control is currently playing. +`repeat` | boolean | `false` | Whether the control will repeat in a continous loop. `_view_module` | string | `'@jupyter-widgets/controls'` | `_view_module_version` | string | `'1.4.0'` | `_view_name` | string | `'PlayView'` |