Merge pull request #61 from pikers/faster_highlighting

Faster row highlighting
kivy_mainline_and_py3.8
goodboy 2018-12-16 21:53:48 -05:00 committed by GitHub
commit b0d4d4b2f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 161 additions and 67 deletions

View File

@ -269,9 +269,10 @@ async def get_cached_feed(
""" """
# check if a cached client is in the local actor's statespace # check if a cached client is in the local actor's statespace
ss = tractor.current_actor().statespace ss = tractor.current_actor().statespace
feeds = ss['feeds'] feeds = ss.setdefault('feeds', {'_lock': trio.Lock()})
lock = feeds['_lock'] lock = feeds['_lock']
feed_stack = ss['feed_stacks'][brokername] feed_stacks = ss.setdefault('feed_stacks', {})
feed_stack = feed_stacks.setdefault(brokername, contextlib.AsyncExitStack())
async with lock: async with lock:
try: try:
feed = feeds[brokername] feed = feeds[brokername]
@ -305,18 +306,15 @@ async def start_quote_stream(
Since most brokers seems to support batch quote requests we Since most brokers seems to support batch quote requests we
limit to one task per process for now. limit to one task per process for now.
""" """
actor = tractor.current_actor() actor = tractor.current_actor()
# set log level after fork # set log level after fork
get_console_log(actor.loglevel) get_console_log(actor.loglevel)
# pull global vars from local actor # pull global vars from local actor
ss = actor.statespace ss = actor.statespace
# broker2symbolsubs = ss.setdefault('broker2symbolsubs', {})
ss.setdefault('feeds', {'_lock': trio.Lock()})
feed_stacks = ss.setdefault('feed_stacks', {})
symbols = list(symbols) symbols = list(symbols)
log.info( log.info(
f"{chan.uid} subscribed to {broker} for symbols {symbols}") f"{chan.uid} subscribed to {broker} for symbols {symbols}")
feed_stack = feed_stacks.setdefault(broker, contextlib.AsyncExitStack())
# another actor task may have already created it # another actor task may have already created it
feed = await get_cached_feed(broker) feed = await get_cached_feed(broker)
symbols2chans = feed.subscriptions[feed_type] symbols2chans = feed.subscriptions[feed_type]

View File

@ -1,60 +0,0 @@
"""Hoverable Behaviour (changing when the mouse is on the widget by O. Poyen.
License: LGPL
"""
__author__ = 'Olivier Poyen'
from kivy.properties import BooleanProperty, ObjectProperty
from kivy.factory import Factory
from kivy.core.window import Window
class HoverBehavior(object):
"""Hover behavior.
:Events:
`on_enter`
Fired when mouse enter the bbox of the widget.
`on_leave`
Fired when the mouse exit the widget.
"""
hovered = BooleanProperty(False)
# Contains the last relevant point received by the Hoverable. This can
# be used in `on_enter` or `on_leave` in order to know where was dispatched
# the event.
border_point = ObjectProperty(None)
def __init__(self, **kwargs):
self.register_event_type('on_enter')
self.register_event_type('on_leave')
Window.bind(mouse_pos=self.on_mouse_pos)
super(HoverBehavior, self).__init__(**kwargs)
def on_mouse_pos(self, *args):
# don't proceed if I'm not displayed <=> If have no parent
if not self.get_root_window():
return
pos = args[1]
# Next line to_widget allow to compensate for relative layout
inside = self.collide_point(*self.to_widget(*pos))
if self.hovered == inside:
# We have already done what was needed
return
self.border_point = pos
self.hovered = inside
if inside:
self.dispatch('on_enter')
else:
self.dispatch('on_leave')
# implement these in the widget impl
def on_enter(self):
pass
def on_leave(self):
pass
# register for global use via kivy.factory.Factory
Factory.register('HoverBehavior', HoverBehavior)

View File

@ -0,0 +1,154 @@
"""Mouse over behaviour.
Based on initial LGPL work by O. Poyen. here:
https://gist.github.com/opqopq/15c707dc4cffc2b6455f
"""
import time
from functools import wraps
from collections import deque
from kivy.properties import BooleanProperty, ObjectProperty
from kivy.core.window import Window
from kivy.clock import Clock
from ...log import get_logger
log = get_logger('kivy')
# XXX: copied from 1.10.1 since the async branch isn't ported yet.
def triggered(timeout=0, interval=False):
'''Decorator that will trigger the call of the function at the specified
timeout, through the method :meth:`CyClockBase.create_trigger`. Subsequent
calls to the decorated function (while the timeout is active) are ignored.
It can be helpful when an expensive funcion (i.e. call to a server) can be
triggered by different methods. Setting a proper timeout will delay the
calling and only one of them wil be triggered.
@triggered(timeout, interval=False)
def callback(id):
print('The callback has been called with id=%d' % id)
>> callback(id=1)
>> callback(id=2)
The callback has been called with id=2
The decorated callback can also be unscheduled using:
>> callback.cancel()
.. versionadded:: 1.10.1
'''
def wrapper_triggered(func):
_args = []
_kwargs = {}
def cb_function(dt):
func(*tuple(_args), **_kwargs)
cb_trigger = Clock.create_trigger(
cb_function,
timeout=timeout,
interval=interval)
@wraps(func)
def trigger_function(*args, **kwargs):
_args[:] = []
_args.extend(list(args))
_kwargs.clear()
_kwargs.update(kwargs)
cb_trigger()
def trigger_cancel():
cb_trigger.cancel()
setattr(trigger_function, 'cancel', trigger_cancel)
return trigger_function
return wrapper_triggered
class MouseOverBehavior(object):
"""Mouse over behavior.
:Events:
`on_enter`
Fired when mouse enter the bbox of the widget.
`on_leave`
Fired when the mouse exit the widget.
"""
hovered = BooleanProperty(False)
# Contains the last relevant point received by the Hoverable. This can
# be used in `on_enter` or `on_leave` in order to know where was dispatched
# the event.
border_point = ObjectProperty(None)
_widgets = deque()
_last_hovered = None
_last_time = time.time()
def __init__(self, **kwargs):
self.register_event_type('on_enter')
self.register_event_type('on_leave')
MouseOverBehavior._widgets.append(self)
super().__init__(**kwargs)
Window.bind(mouse_pos=self._on_mouse_pos)
@classmethod
# try throttling to 1ms latency (doesn't seem to work
# best I can get is 0.01...)
@triggered(timeout=0.001, interval=False)
def _on_mouse_pos(cls, *args):
log.debug(f"{cls} time since last call: {time.time() - cls._last_time}")
cls._last_time = time.time()
# XXX: how to still do this at the class level?
# don't proceed if I'm not displayed <=> If have no parent
# if not self.get_root_window():
# return
pos = args[1]
# Next line to_widget allow to compensate for relative layout
for widget in cls._widgets.copy():
w_coords = widget.to_widget(*pos)
inside = widget.collide_point(*w_coords)
if inside and widget.hovered:
return
elif inside:
# un-highlight the last highlighted
last_hovered = cls._last_hovered
if last_hovered:
last_hovered.dispatch('on_leave')
last_hovered.hovered = False
# stick last highlighted at the front of the stack
# resulting in LIFO iteration for efficiency
cls._widgets.remove(widget)
cls._widgets.appendleft(widget)
cls._last_hovered = widget
# highlight new widget
widget.border_point = pos
widget.hovered = True
widget.dispatch('on_enter')
return
# implement these in the widget impl
@classmethod
def on_enter(cls):
pass
@classmethod
def on_leave(cls):
pass
def new_mouse_over_group():
"""Return a new *mouse over group*, a class that can be mixed
in to a group of widgets which can be mutex highlighted based
on the mouse position.
"""
return type(
'MouseOverBehavior',
(MouseOverBehavior,),
{},
)

View File

@ -23,8 +23,10 @@ from async_generator import aclosing
from ..log import get_logger from ..log import get_logger
from .pager import PagerView from .pager import PagerView
from .kivy.hoverable import HoverBehavior from .kivy.mouse_over import new_mouse_over_group
HoverBehavior = new_mouse_over_group()
log = get_logger('monitor') log = get_logger('monitor')