Skip to content

Commit 386f22b

Browse files
authored
Merge pull request #66 from martinRenou/touch_events
Implement touch events
2 parents 2cf1c77 + 6e5c6c2 commit 386f22b

File tree

3 files changed

+83
-11
lines changed

3 files changed

+83
-11
lines changed

docs/source/events.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@ Those methods take a callback function as single argument, this callback functio
2222
2323
canvas.on_mouse_down(handle_mouse_down)
2424
25+
Built-in touch events
26+
---------------------
27+
28+
The following built-in touch events are supported: ``touch_start``, ``touch_end``, ``touch_move`` and ``touch_cancel``. You can define Python callback functions that will be called whenever those touch events occur, using the ``on_touch_start``, ``on_touch_end``, ``on_touch_move`` and ``on_touch_cancel`` methods.
29+
30+
Those methods take a callback function as single argument, this callback function must take one positional argument which is the list of tuples representing the ``(x, y)`` pixel coordinates where the fingers are located on the canvas.
31+
32+
.. code:: Python
33+
34+
def handle_touch_move(fingers_locations):
35+
# Draw circles where fingers are located
36+
for finger_location in fingers_locations:
37+
canvas.fill_arc(finger_location[0], finger_location[1], 6, 0, 2 * pi)
38+
39+
canvas.on_touch_move(handle_touch_move)
40+
2541
.. note::
2642
Please open an issue or a Pull Request if you want more events to be supported by ipycanvas
2743

ipycanvas/canvas.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,17 @@ class Canvas(CanvasBase):
158158
line_dash_offset = Float(0.)
159159

160160
_client_ready_callbacks = Instance(CallbackDispatcher, ())
161+
161162
_mouse_move_callbacks = Instance(CallbackDispatcher, ())
162163
_mouse_down_callbacks = Instance(CallbackDispatcher, ())
163164
_mouse_up_callbacks = Instance(CallbackDispatcher, ())
164165
_mouse_out_callbacks = Instance(CallbackDispatcher, ())
165166

167+
_touch_start_callbacks = Instance(CallbackDispatcher, ())
168+
_touch_end_callbacks = Instance(CallbackDispatcher, ())
169+
_touch_move_callbacks = Instance(CallbackDispatcher, ())
170+
_touch_cancel_callbacks = Instance(CallbackDispatcher, ())
171+
166172
def __init__(self, *args, **kwargs):
167173
"""Create a Canvas widget."""
168174
#: Whether commands should be cached or not
@@ -494,21 +500,37 @@ def on_client_ready(self, callback, remove=False):
494500
self._client_ready_callbacks.register_callback(callback, remove=remove)
495501

496502
def on_mouse_move(self, callback, remove=False):
497-
"""Register a callback that will be called on mouse mouse_move."""
503+
"""Register a callback that will be called on mouse move."""
498504
self._mouse_move_callbacks.register_callback(callback, remove=remove)
499505

500506
def on_mouse_down(self, callback, remove=False):
501-
"""Register a callback that will be called on mouse mouse_down."""
507+
"""Register a callback that will be called on mouse click down."""
502508
self._mouse_down_callbacks.register_callback(callback, remove=remove)
503509

504510
def on_mouse_up(self, callback, remove=False):
505-
"""Register a callback that will be called on mouse mouse_up."""
511+
"""Register a callback that will be called on mouse click up."""
506512
self._mouse_up_callbacks.register_callback(callback, remove=remove)
507513

508514
def on_mouse_out(self, callback, remove=False):
509-
"""Register a callback that will be called on mouse mouse_out."""
515+
"""Register a callback that will be called on mouse out of the canvas."""
510516
self._mouse_out_callbacks.register_callback(callback, remove=remove)
511517

518+
def on_touch_start(self, callback, remove=False):
519+
"""Register a callback that will be called on touch start (new finger on the screen)."""
520+
self._touch_start_callbacks.register_callback(callback, remove=remove)
521+
522+
def on_touch_end(self, callback, remove=False):
523+
"""Register a callback that will be called on touch end (a finger is not touching the screen anymore)."""
524+
self._touch_end_callbacks.register_callback(callback, remove=remove)
525+
526+
def on_touch_move(self, callback, remove=False):
527+
"""Register a callback that will be called on touch move (finger moving on the screen)."""
528+
self._touch_move_callbacks.register_callback(callback, remove=remove)
529+
530+
def on_touch_cancel(self, callback, remove=False):
531+
"""Register a callback that will be called on touch cancel."""
532+
self._touch_cancel_callbacks.register_callback(callback, remove=remove)
533+
512534
def __setattr__(self, name, value):
513535
super(Canvas, self).__setattr__(name, value)
514536

@@ -542,6 +564,7 @@ def _send_command(self, command, buffers=[]):
542564
def _handle_frontend_event(self, _, content, buffers):
543565
if content.get('event', '') == 'client_ready':
544566
self._client_ready_callbacks()
567+
545568
if content.get('event', '') == 'mouse_move':
546569
self._mouse_move_callbacks(content['x'], content['y'])
547570
if content.get('event', '') == 'mouse_down':
@@ -551,6 +574,15 @@ def _handle_frontend_event(self, _, content, buffers):
551574
if content.get('event', '') == 'mouse_out':
552575
self._mouse_out_callbacks(content['x'], content['y'])
553576

577+
if content.get('event', '') == 'touch_start':
578+
self._touch_start_callbacks([(touch['x'], touch['y']) for touch in content['touches']])
579+
if content.get('event', '') == 'touch_end':
580+
self._touch_end_callbacks([(touch['x'], touch['y']) for touch in content['touches']])
581+
if content.get('event', '') == 'touch_move':
582+
self._touch_move_callbacks([(touch['x'], touch['y']) for touch in content['touches']])
583+
if content.get('event', '') == 'touch_cancel':
584+
self._touch_cancel_callbacks([(touch['x'], touch['y']) for touch in content['touches']])
585+
554586

555587
class MultiCanvas(CanvasBase):
556588
"""Create a MultiCanvas widget with n_canvases Canvas widgets.

src/widget.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,10 @@ class CanvasView extends DOMWidgetView {
343343
this.canvas.addEventListener('mousedown', { handleEvent: this.onMouseDown.bind(this) });
344344
this.canvas.addEventListener('mouseup', { handleEvent: this.onMouseUp.bind(this) });
345345
this.canvas.addEventListener('mouseout', { handleEvent: this.onMouseOut.bind(this) });
346+
this.canvas.addEventListener('touchstart', { handleEvent: this.onTouchStart.bind(this) });
347+
this.canvas.addEventListener('touchend', { handleEvent: this.onTouchEnd.bind(this) });
348+
this.canvas.addEventListener('touchmove', { handleEvent: this.onTouchMove.bind(this) });
349+
this.canvas.addEventListener('touchcancel', { handleEvent: this.onTouchCancel.bind(this) });
346350

347351
this.updateCanvas();
348352
}
@@ -363,23 +367,43 @@ class CanvasView extends DOMWidgetView {
363367
this.canvas.setAttribute('height', size[1]);
364368
}
365369

366-
private onMouseDown(event: MouseEvent) {
367-
this.model.send({ event: 'mouse_down', ...this.getMouseCoordinate(event) }, {});
370+
private onMouseMove(event: MouseEvent) {
371+
this.model.send({ event: 'mouse_move', ...this.getCoordinates(event) }, {});
368372
}
369373

370-
private onMouseMove(event: MouseEvent) {
371-
this.model.send({ event: 'mouse_move', ...this.getMouseCoordinate(event) }, {});
374+
private onMouseDown(event: MouseEvent) {
375+
this.model.send({ event: 'mouse_down', ...this.getCoordinates(event) }, {});
372376
}
373377

374378
private onMouseUp(event: MouseEvent) {
375-
this.model.send({ event: 'mouse_up', ...this.getMouseCoordinate(event) }, {});
379+
this.model.send({ event: 'mouse_up', ...this.getCoordinates(event) }, {});
376380
}
377381

378382
private onMouseOut(event: MouseEvent) {
379-
this.model.send({ event: 'mouse_out', ...this.getMouseCoordinate(event) }, {});
383+
this.model.send({ event: 'mouse_out', ...this.getCoordinates(event) }, {});
384+
}
385+
386+
private onTouchStart(event: TouchEvent) {
387+
const touches: Touch[] = Array.from(event.touches);
388+
this.model.send({ event: 'touch_start', touches: touches.map(this.getCoordinates.bind(this)) }, {});
389+
}
390+
391+
private onTouchEnd(event: TouchEvent) {
392+
const touches: Touch[] = Array.from(event.touches);
393+
this.model.send({ event: 'touch_end', touches: touches.map(this.getCoordinates.bind(this)) }, {});
394+
}
395+
396+
private onTouchMove(event: TouchEvent) {
397+
const touches: Touch[] = Array.from(event.touches);
398+
this.model.send({ event: 'touch_move', touches: touches.map(this.getCoordinates.bind(this)) }, {});
399+
}
400+
401+
private onTouchCancel(event: TouchEvent) {
402+
const touches: Touch[] = Array.from(event.touches);
403+
this.model.send({ event: 'touch_cancel', touches: touches.map(this.getCoordinates.bind(this)) }, {});
380404
}
381405

382-
private getMouseCoordinate(event: MouseEvent) {
406+
private getCoordinates(event: MouseEvent | Touch) {
383407
const rect = this.canvas.getBoundingClientRect();
384408
const x = event.clientX - rect.left;
385409
const y = event.clientY - rect.top;

0 commit comments

Comments
 (0)