1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
|
# -*- coding: utf-8 -*-
# Copyright (c) Vispy Development Team. All Rights Reserved.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
"""
vispy backend for the IPython notebook (vnc approach).
We aim to have:
* ipynb_static - export visualization to a static notebook
* ipynb_vnc - vnc-approach: render in Python, send result to JS as png
* ipynb_webgl - send gl commands to JS and execute in webgl context
"""
from __future__ import division
from ..base import (BaseApplicationBackend, BaseCanvasBackend,
BaseTimerBackend)
from .. import Application, Canvas
from ...util import logger
#from ...util.event import Event # For timer
# Imports for screenshot
# Perhaps we should refactor these to have just one import
from ...gloo.util import _screenshot
from ...io import _make_png
from base64 import b64encode
# Import for displaying Javascript on notebook
import os.path as op
# -------------------------------------------------------------------- init ---
capability = dict( # things that can be set by the backend
title=True, # But it only applies to the dummy window :P
size=True, # We cannot possibly say we dont, because Canvas always sets it
position=True, # Dito
show=True, # Note: we don't alow this, but all scripts call show ...
vsync=False,
resizable=True, # Yes, you can set to not be resizable (it always is)
decorate=False,
fullscreen=False,
context=True,
multi_window=True,
scroll=True,
parent=False,
always_on_top=False,
)
def _set_config(c):
_app.backend_module._set_config(c)
# Init dummy objects needed to import this module without errors.
# These are all overwritten with imports from IPython (on success)
DOMWidget = object
Unicode = Int = Float = Bool = lambda *args, **kwargs: None
# Create our "backend" backend; The toolkit that is going to provide a
# canvas (e.g. OpenGL context) so we can render images.
# Note that if IPython has already loaded a GUI backend, vispy is
# probably going to use that as well, because it prefers loaded backends.
try:
# Explicitly use default (avoid using test-app)
_app = Application('default')
except Exception:
_msg = 'ipynb_vnc backend relies on a core backend'
available, testable, why_not, which = False, False, _msg, None
else:
# Try importing IPython
try:
import IPython
if IPython.version_info < (2,):
raise RuntimeError('ipynb_vnc backend need IPython version >= 2.0')
from IPython.html.widgets import DOMWidget
from IPython.utils.traitlets import Unicode, Int, Float, Bool
from IPython.display import display, Javascript
from IPython.html.nbextensions import install_nbextension
except Exception as exp:
available, testable, why_not, which = False, False, str(exp), None
else:
available, testable, why_not = True, False, None
which = _app.backend_module.which
print(' NOTE: this backend requires the Chromium browser')
# Use that backend's shared context
KEYMAP = _app.backend_module.KEYMAP
# ------------------------------------------------------------- application ---
# todo: maybe trigger something in JS on any of these methods?
class ApplicationBackend(BaseApplicationBackend):
def __init__(self):
BaseApplicationBackend.__init__(self)
self._backend2 = _app._backend
def _vispy_get_backend_name(self):
realname = self._backend2._vispy_get_backend_name()
return 'ipynb_vnc (via %s)' % realname
def _vispy_process_events(self):
return self._backend2._vispy_process_events()
def _vispy_run(self):
pass # We run in IPython, so we don't run!
#return self._backend2._vispy_run()
def _vispy_quit(self):
return self._backend2._vispy_quit()
def _vispy_get_native_app(self):
return self._backend2._vispy_get_native_app()
# ------------------------------------------------------------------ canvas ---
class CanvasBackend(BaseCanvasBackend):
# args are for BaseCanvasBackend, kwargs are for us.
def __init__(self, *args, **kwargs):
BaseCanvasBackend.__init__(self, *args)
self._initialized = False
# Test kwargs
# if kwargs['size']:
# raise RuntimeError('ipynb_vnc Canvas is not resizable')
# if kwargs['position']:
# raise RuntimeError('ipynb_vnc Canvas is not positionable')
if not kwargs['decorate']:
raise RuntimeError('ipynb_vnc Canvas is not decoratable (or not)')
if kwargs['vsync']:
raise RuntimeError('ipynb_vnc Canvas does not support vsync')
if kwargs['fullscreen']:
raise RuntimeError('ipynb_vnc Canvas does not support fullscreen')
# Create real canvas. It is a backend to this backend
kwargs.pop('vispy_canvas', None)
kwargs['autoswap'] = False
canvas = Canvas(app=_app, **kwargs) # Pass kwargs to underlying canvas
self._backend2 = canvas.native
# Connect to events of canvas to keep up to date with size and draws
canvas.events.draw.connect(self._on_draw)
canvas.events.resize.connect(self._on_resize)
# Show the widget, we will hide it after the first time it's drawn
self._backend2._vispy_set_visible(True)
self._need_draw = False
# Prepare Javascript code by displaying on notebook
self._prepare_js()
# Create IPython Widget
self._widget = Widget(self._gen_event, size=canvas.size)
def _vispy_warmup(self):
return self._backend2._vispy_warmup()
def _vispy_set_current(self):
return self._backend2._vispy_set_current()
def _vispy_swap_buffers(self):
return self._backend2._vispy_swap_buffers()
def _vispy_set_title(self, title):
return self._backend2._vispy_set_title(title)
#logger.warning('IPython notebook canvas has not title.')
def _vispy_set_size(self, w, h):
#logger.warn('IPython notebook canvas cannot be resized.')
res = self._backend2._vispy_set_size(w, h)
self._backend2._vispy_set_visible(True)
return res
def _vispy_set_position(self, x, y):
logger.warning('IPython notebook canvas cannot be repositioned.')
def _vispy_set_visible(self, visible):
#self._backend2._vispy_set_visible(visible)
if not visible:
logger.warning('IPython notebook canvas cannot be hidden.')
else:
display(self._widget)
def _vispy_update(self):
self._need_draw = True
return self._backend2._vispy_update()
def _vispy_close(self):
self._need_draw = False
self._widget.quit()
return self._backend2._vispy_close()
def _vispy_get_position(self):
return 0, 0
def _vispy_get_size(self):
return self._backend2._vispy_get_size()
def _on_resize(self, event=None):
# Event handler that is called by the underlying canvas
if self._vispy_canvas is None:
return
size = self._backend2._vispy_get_size()
self._widget.size = size
self._vispy_canvas.events.resize(size=size)
def _on_draw(self, event=None):
# Event handler that is called by the underlying canvas
if self._vispy_canvas is None:
return
# Handle initialization
if not self._initialized:
self._initialized = True
#self._vispy_canvas.events.add(timer=Event)
self._vispy_canvas.events.initialize()
self._on_resize()
# We are drawn, so no need for a redraw
self._need_draw = False
# We hide the widget once it has received a paint event. So at
# initialization and after a resize the widget is briefly visible.
# Now that it is hidden the widget is unlikely to receive paint
# events anymore, so we need to force repaints from now on, via
# a trigger from JS.
self._backend2._vispy_set_visible(False)
# Normal behavior
self._vispy_canvas.set_current()
self._vispy_canvas.events.draw(region=None)
# Save the encoded screenshot image to widget
self._save_screenshot()
def _save_screenshot(self):
# Take the screenshot
img = _screenshot()
# Convert to PNG and encode
self._widget.value = b64encode(_make_png(img))
# Generate vispy events according to upcoming JS events
def _gen_event(self, ev):
if self._vispy_canvas is None:
return
ev = ev.get("event")
# Parse and generate event
if ev.get("name") == "MouseEvent":
mouse = ev.get("properties")
# Generate
if mouse.get("type") == "mouse_move":
self._vispy_mouse_move(native=mouse,
pos=mouse.get("pos"),
modifiers=mouse.get("modifiers"),
)
elif mouse.get("type") == "mouse_press":
self._vispy_mouse_press(native=mouse,
pos=mouse.get("pos"),
button=mouse.get("button"),
modifiers=mouse.get("modifiers"),
)
elif mouse.get("type") == "mouse_release":
self._vispy_mouse_release(native=mouse,
pos=mouse.get("pos"),
button=mouse.get("button"),
modifiers=mouse.get("modifiers"),
)
elif mouse.get("type") == "mouse_wheel":
self._vispy_canvas.events.mouse_wheel(native=mouse,
delta=mouse.get("delta"),
pos=mouse.get("pos"),
modifiers=mouse.get
("modifiers"),
)
elif ev.get("name") == "KeyEvent":
key = ev.get("properties")
if key.get("type") == "key_press":
self._vispy_canvas.events.key_press(native=key,
key=key.get("key"),
text=key.get("text"),
modifiers=key.get
("modifiers"),
)
elif key.get("type") == "key_release":
self._vispy_canvas.events.key_release(native=key,
key=key.get("key"),
text=key.get("text"),
modifiers=key.get
("modifiers"),
)
elif ev.get("name") == "PollEvent": # Ticking from front-end (JS)
# Allthough the event originates from JS, this is basically
# a poll event from IPyhon's event loop, which we use to
# update the backend app and draw stuff if necessary. If we
# succeed to make IPython process GUI app events directly,
# this "JS timer" should not be necessary.
self._vispy_canvas.app.process_events()
if self._need_draw:
self._on_draw()
# Generate a timer event on every poll from JS
# AK: no, just use app.Timer as usual!
#self._vispy_canvas.events.timer(type="timer")
def _prepare_js(self):
pkgdir = op.dirname(__file__)
install_nbextension([op.join(pkgdir, '../../html/static/js')])
script = 'IPython.load_extensions("js/vispy");'
display(Javascript(script))
# ------------------------------------------------------------------- timer ---
class TimerBackend(BaseTimerBackend):
def __init__(self, vispy_timer):
self._backend2 = _app.backend_module.TimerBackend(vispy_timer)
def _vispy_start(self, interval):
return self._backend2._vispy_start(interval)
def _vispy_stop(self):
return self._backend2._vispy_stop()
def _vispy_timeout(self):
return self._backend2._vispy_timeout()
# ---------------------------------------------------------- IPython Widget ---
class Widget(DOMWidget):
_view_name = Unicode("Widget", sync=True)
# Define the custom state properties to sync with the front-end
format = Unicode('png', sync=True)
width = Int(sync=True)
height = Int(sync=True)
interval = Float(sync=True)
is_closing = Bool(sync=True)
value = Unicode(sync=True)
def __init__(self, gen_event, **kwargs):
super(Widget, self).__init__(**kwargs)
self.size = kwargs["size"]
self.interval = 50.0
self.gen_event = gen_event
self.on_msg(self._handle_event_msg)
def _handle_event_msg(self, _, content):
# If closing, don't bother generating the event
if not self.is_closing:
self.gen_event(content)
@property
def size(self):
return self.width, self.height
@size.setter
def size(self, size):
self.width, self.height = size
def quit(self):
self.is_closing = True
self.close()
|