Skip to content

Commit 40ee544

Browse files
authored
Merge pull request #6 from martinRenou/multi_canvases
Implement MultiCanvas
2 parents 7f92584 + 5acdfcc commit 40ee544

File tree

5 files changed

+230
-6
lines changed

5 files changed

+230
-6
lines changed

examples/MultiCanvas.ipynb

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# MultiCanvas\n",
8+
"\n",
9+
"A `MultiCanvas` allows you to have multiple canvases one on top of the other. This is very useful when you need multiple layers that don't have the same update frequency (*e.g.* if you make a Tetris game you might only want to render the background image once, and never draw it again)."
10+
]
11+
},
12+
{
13+
"cell_type": "code",
14+
"execution_count": null,
15+
"metadata": {},
16+
"outputs": [],
17+
"source": [
18+
"from ipycanvas import MultiCanvas"
19+
]
20+
},
21+
{
22+
"cell_type": "code",
23+
"execution_count": null,
24+
"metadata": {},
25+
"outputs": [],
26+
"source": [
27+
"m = MultiCanvas(n_canvases=3, size=(200, 200))"
28+
]
29+
},
30+
{
31+
"cell_type": "code",
32+
"execution_count": null,
33+
"metadata": {},
34+
"outputs": [],
35+
"source": [
36+
"m"
37+
]
38+
},
39+
{
40+
"cell_type": "code",
41+
"execution_count": null,
42+
"metadata": {},
43+
"outputs": [],
44+
"source": [
45+
"def draw_square(canvas, pos, width, color):\n",
46+
" canvas.fill_style = color\n",
47+
" canvas.fill_rect(pos, pos, width, width)"
48+
]
49+
},
50+
{
51+
"cell_type": "code",
52+
"execution_count": null,
53+
"metadata": {},
54+
"outputs": [],
55+
"source": [
56+
"draw_square(m[0], 0, 200, 'orange')"
57+
]
58+
},
59+
{
60+
"cell_type": "code",
61+
"execution_count": null,
62+
"metadata": {},
63+
"outputs": [],
64+
"source": [
65+
"draw_square(m[1], 50, 100, 'blue')"
66+
]
67+
},
68+
{
69+
"cell_type": "code",
70+
"execution_count": null,
71+
"metadata": {},
72+
"outputs": [],
73+
"source": [
74+
"draw_square(m[2], 75, 50, 'green')"
75+
]
76+
},
77+
{
78+
"cell_type": "code",
79+
"execution_count": null,
80+
"metadata": {},
81+
"outputs": [],
82+
"source": [
83+
"draw_square(m[0], 0, 200, 'green')"
84+
]
85+
},
86+
{
87+
"cell_type": "code",
88+
"execution_count": null,
89+
"metadata": {},
90+
"outputs": [],
91+
"source": [
92+
"m[1].clear()"
93+
]
94+
},
95+
{
96+
"cell_type": "code",
97+
"execution_count": null,
98+
"metadata": {},
99+
"outputs": [],
100+
"source": [
101+
"draw_square(m[0], 0, 200, 'orange')"
102+
]
103+
}
104+
],
105+
"metadata": {
106+
"kernelspec": {
107+
"display_name": "Python 3",
108+
"language": "python",
109+
"name": "python3"
110+
},
111+
"language_info": {
112+
"codemirror_mode": {
113+
"name": "ipython",
114+
"version": 3
115+
},
116+
"file_extension": ".py",
117+
"mimetype": "text/x-python",
118+
"name": "python",
119+
"nbconvert_exporter": "python",
120+
"pygments_lexer": "ipython3",
121+
"version": "3.7.3"
122+
}
123+
},
124+
"nbformat": 4,
125+
"nbformat_minor": 4
126+
}

ipycanvas/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Copyright (c) Martin Renou.
55
# Distributed under the terms of the Modified BSD License.
66

7-
from .canvas import Canvas, hold_canvas # noqa
7+
from .canvas import Canvas, MultiCanvas, hold_canvas # noqa
88
from ._version import __version__, version_info # noqa
99

1010
from .nbextension import _jupyter_nbextension_paths # noqa

ipycanvas/_frontend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
"""
1010

1111
module_name = "ipycanvas"
12-
module_version = "^0.1.0"
12+
module_version = "^0.2.0"

ipycanvas/canvas.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
from contextlib import contextmanager
88

9-
from ipywidgets import Color, DOMWidget
9+
from traitlets import Float, Instance, List, Tuple, Unicode, observe
1010

11-
from traitlets import Float, Tuple, Unicode, observe
11+
from ipywidgets import Color, DOMWidget, widget_serialization
1212

1313
from ._frontend import module_name, module_version
1414

@@ -39,6 +39,7 @@ class Canvas(DOMWidget):
3939
direction = Unicode('inherit')
4040

4141
def __init__(self, *args, **kwargs):
42+
"""Create a Canvas widget."""
4243
self.caching = kwargs.get('caching', False)
4344
self._commands_cache = []
4445

@@ -168,6 +169,36 @@ def _send_command(self, command):
168169
self.send(command)
169170

170171

172+
class MultiCanvas(DOMWidget):
173+
_model_name = Unicode('MultiCanvasModel').tag(sync=True)
174+
_model_module = Unicode(module_name).tag(sync=True)
175+
_model_module_version = Unicode(module_version).tag(sync=True)
176+
_view_name = Unicode('MultiCanvasView').tag(sync=True)
177+
_view_module = Unicode(module_name).tag(sync=True)
178+
_view_module_version = Unicode(module_version).tag(sync=True)
179+
180+
size = Tuple((700, 500), help='Size of the Canvas, this is not equal to the size of the view')
181+
182+
_canvases = List(Instance(Canvas)).tag(sync=True, **widget_serialization)
183+
184+
def __init__(self, n_canvases=3, *args, **kwargs):
185+
"""Create a MultiCanvas widget with n_canvases Canvas widgets."""
186+
size = kwargs.get('size', (700, 500))
187+
188+
super(MultiCanvas, self).__init__(*args, _canvases=[Canvas(size=size) for _ in range(n_canvases)], **kwargs)
189+
self.layout.width = str(size[0]) + 'px'
190+
self.layout.height = str(size[1]) + 'px'
191+
192+
def __getitem__(self, key):
193+
"""Access one of the Canvas instances."""
194+
return self._canvases[key]
195+
196+
@observe('size')
197+
def _on_size_change(self, change):
198+
for canvas in self._canvases:
199+
canvas.size = change.new
200+
201+
171202
@contextmanager
172203
def hold_canvas(canvas):
173204
"""Hold any drawing on the canvas, and perform only one draw command at the end."""

src/widget.ts

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Distributed under the terms of the Modified BSD License.
33

44
import {
5-
DOMWidgetModel, DOMWidgetView, ISerializers, Dict
5+
DOMWidgetModel, DOMWidgetView, ISerializers, Dict, ViewList, unpack_models
66
} from '@jupyter-widgets/base';
77

88
import {
@@ -20,7 +20,7 @@ class CanvasModel extends DOMWidgetModel {
2020
_view_name: CanvasModel.view_name,
2121
_view_module: CanvasModel.view_module,
2222
_view_module_version: CanvasModel.view_module_version,
23-
size: [],
23+
size: [700, 500],
2424
};
2525
}
2626

@@ -136,3 +136,70 @@ class CanvasView extends DOMWidgetView {
136136

137137
model: CanvasModel;
138138
}
139+
140+
141+
export
142+
class MultiCanvasModel extends DOMWidgetModel {
143+
defaults() {
144+
return {...super.defaults(),
145+
_model_name: MultiCanvasModel.model_name,
146+
_model_module: MultiCanvasModel.model_module,
147+
_model_module_version: MultiCanvasModel.model_module_version,
148+
_view_name: MultiCanvasModel.view_name,
149+
_view_module: MultiCanvasModel.view_module,
150+
_view_module_version: MultiCanvasModel.view_module_version,
151+
_canvases: [],
152+
};
153+
}
154+
155+
static serializers: ISerializers = {
156+
...DOMWidgetModel.serializers,
157+
_canvases: { deserialize: (unpack_models as any) },
158+
}
159+
160+
static model_name = 'MultiCanvasModel';
161+
static model_module = MODULE_NAME;
162+
static model_module_version = MODULE_VERSION;
163+
static view_name = 'MultiCanvasView';
164+
static view_module = MODULE_NAME;
165+
static view_module_version = MODULE_VERSION;
166+
}
167+
168+
169+
export
170+
class MultiCanvasView extends DOMWidgetView {
171+
render() {
172+
this.container = document.createElement('div');
173+
this.container.style.position = 'relative';
174+
175+
this.el.appendChild(this.container);
176+
177+
this.canvas_views = new ViewList<CanvasView>(this.createCanvasView, this.removeCanvasView, this);
178+
this.updateCanvasViews();
179+
180+
this.model.on('change:_canvases', this.updateCanvasViews.bind(this));
181+
}
182+
183+
private updateCanvasViews() {
184+
this.canvas_views.update(this.model.get('_canvases'));
185+
}
186+
187+
private createCanvasView(canvasModel: CanvasModel, index: number) {
188+
// The following ts-ignore is needed due to ipywidgets's implementation
189+
// @ts-ignore
190+
return this.create_child_view(canvasModel).then((canvasView: CanvasView) => {
191+
canvasView.el.style.zIndex = index;
192+
canvasView.el.style.position = 'absolute';
193+
this.container.appendChild(canvasView.el);
194+
195+
return canvasView;
196+
});
197+
}
198+
199+
private removeCanvasView(canvasView: CanvasView) {
200+
this.container.removeChild(canvasView.el);
201+
}
202+
203+
private container: HTMLDivElement;
204+
private canvas_views: ViewList<CanvasView>;
205+
}

0 commit comments

Comments
 (0)