def _tooltip_handler(self, e): ev = create_pointer_event(self.node, e) x, y = ev.pos # Get tooltip object - if text is None it means no tooltip ob = self._tooltips.pick(x, y) if ob is not None and not ob.text: ob = None # Handle touch events - show tt during a touch, but only after a delay delay = 400 if e.type == "touchstart": delay = 200 elif e.type == "touchend" or e.type == "touchcancel" or e.type == "touchmove": ob = None # Update our tooltip div if ob is not None: if e.type == "mousedown": # down -> hide while we're over it self._tooltipdiv.style.display = "none" elif self._mouse_tracking: # dont show tooltips while dragging pass elif self._tooltipdiv.rect != ob.rect: # note: deep comparison # Prepare for showing tooltip, then show after a delay self._tooltipdiv.rect = ob.rect self._tooltipdiv.innerText = ob.text self._tooltipdiv.style.display = "block" self._tooltipdiv.style.transition = "none" self._tooltipdiv.style.opacity = 0 if ob.positioning == "mouse": self._tooltipdiv.ypos = y - 20 elif ob.positioning == "below": self._tooltipdiv.ypos = ob.rect[3] else: # ob.positioning == "ob" self._tooltipdiv.ypos = ob.rect[1] self._tooltipdiv.style.top = self._tooltipdiv.ypos + "px" if x < self.w - 200: if ob.positioning == "below": self._tooltipdiv.xpos = ob.rect[0] - 10 else: self._tooltipdiv.xpos = ob.rect[2] self._tooltipdiv.style.left = self._tooltipdiv.xpos + "px" self._tooltipdiv.style.right = None else: if ob.positioning == "below": self._tooltipdiv.xpos = self.w - ob.rect[2] - 10 else: self._tooltipdiv.xpos = self.w - ob.rect[0] self._tooltipdiv.style.left = None self._tooltipdiv.style.right = self._tooltipdiv.xpos + "px" window.setTimeout(self._tooltip_show, delay) elif self._tooltipdiv.rect is not None: # Hide tooltip, really un-display the div after a delay self._tooltipdiv.style.opacity = 0 if self._tooltipdiv.style.left: self._tooltipdiv.style.left = self._tooltipdiv.rect[2] + "px" else: self._tooltipdiv.style.right = self.w - self._tooltipdiv.rect[ 0] + "px" self._tooltipdiv.rect = None window.setTimeout(self._tooltip_hide, 300)
def on_ws_message(evt): msg = evt.data or evt # bsdf-encoded command if self._pending_commands is None: # Direct mode self._receive_raw_command(msg) else: # Indirect mode, to give browser draw-time during loading if len(self._pending_commands) == 0: window.setTimeout(self._process_commands, 0) self._pending_commands.push(msg)
def update(self, asap=True): """Schedule an update.""" # The extra setTimeout is to make sure that there is time for the # browser to process events (like scrolling). if not self._pending_draw: self._pending_draw = True if asap: window.requestAnimationFrame(self._draw) else: window.setTimeout(window.requestAnimationFrame, 10, self._draw)
def _draw_tick(self): """Function that calls update() to schedule a draw() on a regular interval. Where regular is really regular, so that second-ticks don't "jump". This functon must *not* be called more than once. """ now = window.Date().getTime() res = 1000 # 1 FPS etime = int(now / res) * res + res window.setTimeout(self._draw_tick, etime - now) self.update()
def on_ws_message(evt): msg = evt.data # bsdf-encoded command if not msg: pass # ? drop glitchy message :/ elif self._pending_commands is None: # Direct mode self._receive_raw_command(msg) else: # Indirect mode, to give browser draw-time during loading if len(self._pending_commands) == 0: window.setTimeout(self._process_commands, 0) self._pending_commands.push(msg)
def _process_commands(self): """ A less direct way to process commands, which gives the browser time to draw about every other JS asset. This is a tradeoff between a smooth spinner and fast load time. """ while self._pending_commands is not None and len(self._pending_commands) > 0: msg = self._pending_commands.pop(0) try: command = self._receive_raw_command(msg) except Exception as err: window.setTimeout(self._process_commands, 0) raise err if command[0] == 'DEFINE': self._asset_count += 1 if (self._asset_count % 3) == 0: if len(self._pending_commands): window.setTimeout(self._process_commands, 0) break
def _set_state(self, state, timeout=0): """Set the state, now or somewhat later.""" window.clearTimeout(self._state_timeout) self._state_timeout = None if timeout > 0: self._state_timeout = window.setTimeout(self._set_state, timeout * 1000, state) else: self.state = state
def _init_events(self): # Disable context menu so we can handle RMB clicks # Firefox is particularly stuborn with Shift+RMB, and RMB dbl click for ev_name in ("contextmenu", "click", "dblclick"): window.document.addEventListener(ev_name, self._prevent_default_event, 0) # Keep track of wheel event directed at the canvas self.node.addEventListener("wheel", self._on_js_wheel_event, 0) # If the canvas uses the wheel event for something, you'd want to # disable browser-scroll when the mouse is over the canvas. But # when you scroll down a page and the cursor comes over the canvas # because of that, we don't want the canvas to capture too eagerly. # This code only captures if there has not been scrolled elsewhere # for about half a second. if not window._wheel_timestamp: window._wheel_timestamp = 0, "" window.document.addEventListener("wheel", self._on_js_wheel_global, 0) # Keep track of mouse events self.node.addEventListener("mousedown", self._on_js_mouse_event, 0) window.document.addEventListener("mouseup", self._on_js_mouse_event, 0) window.document.addEventListener("mousemove", self._on_js_mouse_event, 0) window.document.addEventListener("mousemove", self._tooltip_handler, 0) self.node.addEventListener("mousedown", self._tooltip_handler, 0) self.node.addEventListener("touchstart", self._tooltip_handler, 0) self.node.addEventListener("touchmove", self._tooltip_handler, 0) self.node.addEventListener("touchend", self._tooltip_handler, 0) self.node.addEventListener("touchcancel", self._tooltip_handler, 0) # Keep track of touch events self.node.addEventListener("touchstart", self._on_js_touch_event, 0) self.node.addEventListener("touchend", self._on_js_touch_event, 0) self.node.addEventListener("touchcancel", self._on_js_touch_event, 0) self.node.addEventListener("touchmove", self._on_js_touch_event, 0) # Keep track of window size window.addEventListener("resize", self._on_js_resize_event, False) window.setTimeout(self._on_js_resize_event, 10)
def _receive_command(self, command): """ Process a command send from the server. """ cmd = command[0] if cmd == 'PING': # Used for roundtrip stuff, do at least one iter loop here ... window.setTimeout(self.send_command, 10, 'PONG', command[1]) elif cmd == 'INIT_DONE': window.flexx.spin(None) while len(self._pending_commands): self._receive_raw_command(self._pending_commands.pop(0)) self._pending_commands = None # print('init took', time() - self._init_time) elif cmd == 'PRINT': (window.console.ori_log or window.console.log)(command[1]) elif cmd == 'EXEC': eval(command[1]) elif cmd == 'EVAL': x = None if len(command) == 2: x = eval(command[1]) elif len(command) == 3: x = eval('this.instances.' + command[1] + '.' + command[2]) console.log(str(x)) # print (and thus also sends back result) elif cmd == 'EVALANDRETURN': try: x = eval(command[1]) except Exception as err: x = str(err) eval_id = command[2] # to identify the result in Python self.send_command("EVALRESULT", x, eval_id) elif cmd == 'INVOKE': id, name, args = command[1:] ob = self.instances.get(id, None) if ob is None: console.warn('Cannot invoke %s.%s; ' 'session does not know it (anymore).' % (id, name)) elif ob._disposed is True: pass # deleted, but other end might not be aware when command was send else: ob[name](*args) elif cmd == 'INSTANTIATE': self.instantiate_component( *command[1:]) # module, cname, id, args, kwargs elif cmd == 'DISPOSE': id = command[1] c = self.instances.get(id, None) if c is not None and c._disposed is False: # else: no need to warn c._dispose() self.send_command('DISPOSE_ACK', command[1]) self.instances.pop(id, None) # Drop local reference now elif cmd == 'DISPOSE_ACK': self.instances.pop(command[1], None) # Drop reference elif cmd == 'DEFINE': #and command[1] == 'JS' or command[1] == 'DEFINE-JS-EVAL '): kind, name, code = command[1:] window.flexx.spin() address = window.location.protocol + '//' + self.ws_url.split( '/')[2] code += '\n//# sourceURL=%s/flexx/assets/shared/%s\n' % (address, name) if kind == 'JS-EVAL': eval(code) elif kind == 'JS': # With this method, sourceURL does not work on Firefox, # but eval might not work for assets that don't "use strict" # (e.g. Bokeh). Note, btw, that creating links to assets does # not work because these won't be loaded on time. el = window.document.createElement("script") el.id = name el.innerHTML = code window.flexx.asset_node.appendChild(el) elif kind == 'CSS': el = window.document.createElement("style") el.type = "text/css" el.id = name el.innerHTML = code window.flexx.asset_node.appendChild(el) else: window.console.error('Dont know how to DEFINE ' + name + ' with "' + kind + '".') elif cmd == 'OPEN': window.win1 = window.open(command[1], 'new', 'chrome') else: window.console.error('Invalid command: "' + cmd + '"') return command
def on_resize(): window.setTimeout(_on_resize, 1)
def sync_soon(self, timeout=10): """Invoke a sync action. Cancel the pending sync action.""" self.sync_time = dt.now(), dt.now() + timeout window.clearTimeout(self._sync_timeout) self._sync_timeout = window.setTimeout(self._sync_callback, timeout * 1000)
def _receive_pong(self, count): while len(self._ping_calls) > 0 and self._ping_calls[0][0] <= count: _, callback, args = self._ping_calls.pop(0) window.setTimeout(callback, 0, *args)
def call_after_roundtrip(self, callback, *args): ping_to_schedule_at = self._ping_counter + 1 if len(self._ping_calls) == 0 or self._ping_calls[-1][0] < ping_to_schedule_at: window.setTimeout(self._send_ping, 0) self._ping_calls.push((ping_to_schedule_at, callback, args))
def _receive_command(self, command): """ Process a command send from the server. """ cmd = command[0] if cmd == 'PING': # Used for roundtrip stuff, do at least one iter loop here ... window.setTimeout(self.send_command, 10, 'PONG', command[1]) elif cmd == 'INIT_DONE': window.flexx.spin(None) while len(self._pending_commands): self._receive_raw_command(self._pending_commands.pop(0)) self._pending_commands = None # print('init took', time() - self._init_time) elif cmd == 'PRINT': (window.console.ori_log or window.console.log)(command[1]) elif cmd == 'EXEC': eval(command[1]) elif cmd == 'EVAL': x = None if len(command) == 2: x = eval(command[1]) elif len(command) == 3: x = eval('this.instances.' + command[1] + '.' + command[2]) console.log(str(x)) # print (and thus also sends back result) elif cmd == 'EVALANDRETURN': try: x = eval(command[1]) except Exception as err: x = str(err) eval_id = command[2] # to identify the result in Python self.send_command("EVALRESULT", x, eval_id) elif cmd == 'INVOKE': id, name, args = command[1:] ob = self.instances.get(id, None) if ob is None: console.warn('Cannot invoke %s.%s; ' 'session does not know it (anymore).' % (id, name)) elif ob._disposed is True: pass # deleted, but other end might not be aware when command was send else: ob[name](*args) elif cmd == 'INSTANTIATE': self.instantiate_component(*command[1:]) # module, cname, id, args, kwargs elif cmd == 'DISPOSE': id = command[1] c = self.instances.get(id, None) if c is not None and c._disposed is False: # else: no need to warn c._dispose() self.send_command('DISPOSE_ACK', command[1]) self.instances.pop(id, None) # Drop local reference now elif cmd == 'DISPOSE_ACK': self.instances.pop(command[1], None) # Drop reference elif cmd == 'DEFINE': #and command[1] == 'JS' or command[1] == 'DEFINE-JS-EVAL '): kind, name, code = command[1:] window.flexx.spin() address = window.location.protocol + '//' + self.ws_url.split('/')[2] code += '\n//# sourceURL=%s/flexx/assets/shared/%s\n' % (address, name) if kind == 'JS-EVAL': eval(code) elif kind == 'JS': # With this method, sourceURL does not work on Firefox, # but eval might not work for assets that don't "use strict" # (e.g. Bokeh). Note, btw, that creating links to assets does # not work because these won't be loaded on time. el = window.document.createElement("script") el.id = name el.innerHTML = code window.flexx.asset_node.appendChild(el) elif kind == 'CSS': el = window.document.createElement("style") el.type = "text/css" el.id = name el.innerHTML = code window.flexx.asset_node.appendChild(el) else: window.console.error('Dont know how to DEFINE ' + name + ' with "' + kind + '".') elif cmd == 'OPEN': window.win1 = window.open(command[1], 'new', 'chrome') else: window.console.error('Invalid command: "' + cmd + '"') return command
def call_after_roundtrip(self, callback, *args): ping_to_schedule_at = self._ping_counter + 1 if len(self._ping_calls ) == 0 or self._ping_calls[-1][0] < ping_to_schedule_at: window.setTimeout(self._send_ping, 0) self._ping_calls.push((ping_to_schedule_at, callback, args))
def init(self): self.node.id = self.id window.setTimeout(self.load_viz, 500)