Sped up the algorithm of checking mouse enter and leave events.

This commit is contained in:
Joey Payne 2014-02-11 16:03:10 -07:00
commit fc8fc312a9
4 changed files with 118 additions and 105 deletions

View file

@ -1,6 +1,6 @@
from __future__ import print_function
from jgui.settings import DEBUG
import jgui.settings as settings
def log(*args):
if DEBUG:
if settings.DEBUG:
print(*args)

View file

@ -6,22 +6,41 @@ import math
from .structures import Size, Position, Rectangle, Color, BorderRadius, Padding, Gradient, RadialGradient
from ..events.events import WindowEventSource
from ..logger import log
from datetime import datetime
from jgui.settings import DEBUG as debug
debug = True
class Surface(object):
def __init__(self, size=None, context=None, render_mouse=True):
class Surface(WindowEventSource):
def __init__(self, size=None, context=None, data=None, render_mouse=True, show_fps=False):
super(Surface, self).__init__()
self.show_fps = show_fps
self.size = Size.from_value(size)
self.mouse_pos = Position()
if context is None:
self.csurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.size.width, self.size.height)
if data is not None:
self.csurface = cairo.ImageSurface.create_for_data(data, cairo.FORMAT_ARGB32, self.size.width, self.size.height)
else:
self.csurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.size.width, self.size.height)
self.context = cairo.Context(self.csurface)
else:
self.context = context
self.root_window = Window('root', position=Position(0,0), size=self.size, context=self.context)
self.root_window = Window('root', position=Position(0,0), size=self.size, context=self.context, surface=self)
self.render_mouse = render_mouse
if self.render_mouse:
self.mouse_icon = Mouse('mouse', position=Position(0, 0), size=Size(12,20), context=self.context)
self.windows = [self.root_window, self.mouse_icon]
self.mouse_icon = Mouse('mouse', position=Position(0, 0), size=Size(12,20), context=self.context, surface=self)
self.fps_counter = TextWindow('fps', '0 fps', position=Position(self.size.width-70, 10), size=Size(80,20), context=self.context, surface=self)
self.windows = [self.root_window, self.mouse_icon, self.fps_counter]
self.current_hover_window = self.root_window
self.current_focused_window = self.root_window
self.dt = 0
self.old_time = datetime.now()
self.dtindex = 0
self.max_samples = 60
self.dtlist = range(self.max_samples)
self.accept('mouse-move', self.process_mouse_move)
def setTopZero(self, context):
context.identity_matrix()
@ -29,17 +48,57 @@ class Surface(object):
1, 0, 0)
context.transform(matrix)
def inject_mouse_position(self, pos):
if self.render_mouse:
self.mouse_icon.position = pos
self.root_window.inject_mouse_position(pos)
def inject_mouse_down(self, button):
self.current_focused_window = self.current_hover_window
self.root_window.inject_mouse_down(button)
def inject_mouse_up(self, button):
self.root_window.inject_mouse_up(button)
def inject_mouse_position(self, pos):
mouse_pos = Position.from_value(pos)
diff = mouse_pos - self.mouse_pos
old_pos = self.mouse_pos
self.mouse_pos = mouse_pos
if diff != Position(0,0):
self.dispatch('mouse-move', self, old_pos, self.mouse_pos)
if self.render_mouse:
self.mouse_icon.position = pos
self.root_window.inject_mouse_position(pos)
def mouse_inside(self):
"""
Checks if the mouse is inside the window taking into account
all other windows and draw priorities.
"""
#Check all children of root to see if there is a higher priority
#window than the current one
root = self.root_window
stack = [root]
visited = set()
while stack:
item = stack[-1]
rec = item.rectangle
clip_parent = item.get_clip_parent()
if clip_parent is not None:
rec = item.rectangle.intersection(clip_parent.rectangle)
intersects_mouse = rec.intersects_with(self.mouse_pos)
if item.children and not set(item.children).issubset(visited):
stack.extend(item.children)
else:
if intersects_mouse:
return item
visited.add(item)
stack.pop()
return None
def process_mouse_move(self, obj, old_mpos, new_mpos):
if not self.current_hover_window or not self.current_hover_window.mouse_down:
self.current_hover_window = self.mouse_inside()
def notify_window_resize(self, width, height):
self.size = Size(width, height)
self.root_window.size = self.size
@ -53,9 +112,21 @@ class Surface(object):
if item.children:
stack.extend(item.children)
def calcfps(self, dt):
self.dtlist[self.dtindex] = float(dt)
self.dtindex += 1
if self.dtindex >= self.max_samples:
self.dtindex = 0
return 1.0/(sum(self.dtlist)/float(self.max_samples))
def draw(self):
#self.setTopZero(ctx)
#self.context.identity_matrix()
if self.show_fps:
self.dt = (datetime.now() - self.old_time).total_seconds()
self.old_time = datetime.now()
fps = self.calcfps(self.dt)
self.fps_counter.text = '{} fps'.format(round(fps))
self.context.set_operator(cairo.OPERATOR_CLEAR)
self.context.rectangle(0.0, 0.0, self.size.width, self.size.height)
self.context.fill()
@ -64,7 +135,6 @@ class Surface(object):
window.draw()
class WindowSurface(object):
line_joins = {'miter': cairo.LINE_JOIN_MITER,
@ -118,7 +188,7 @@ class WindowSurface(object):
def draw_circle(self, position, size, color=(1,1,1,1), line_width=1.0, line_color=(0,0,0,1), start_angle=0.0, end_angle=360.0):
color = Color.from_value(color)
line_color = Color.from_value(line_color)
context = self.context
context = self.surface.context
position = Position.from_value(position)
size = Size.from_value(size)
width = size.width
@ -148,7 +218,7 @@ class WindowSurface(object):
center_horizontal=True,
center_vertical=True, image_offset=(0, 0)):
context = self.context
context = self.surface.context
position = Position.from_value(position)
offset = Position.from_value(image_offset)
size = Size.from_value(size)
@ -158,9 +228,6 @@ class WindowSurface(object):
x,y = (self.position.x+position.x,
self.position.y+position.y)
context.rectangle(x, y, width, height)
context.clip()
if isinstance(image, basestring):
image = self.load_image(image)
@ -229,7 +296,7 @@ class WindowSurface(object):
color = Color.from_value(font_color)
background_color = Color.from_value(background_color)
position = Position.from_value(position)
context = self.context
context = self.surface.context
font_weight = self.font_weights[font_weight]
font_style = self.font_styles[font_style]
@ -264,7 +331,7 @@ class WindowSurface(object):
if lines:
line_color = Color.from_value(line_color)
background_color = Color.from_value(background_color)
context = self.context
context = self.surface.context
start_pos = self.position + lines[0]
context.set_line_width(line_width)
context.move_to(start_pos.x, start_pos.y)
@ -286,7 +353,7 @@ class WindowSurface(object):
context.stroke()
def render_radial_gradient(self, gradient, inner_radius=None, outer_radius=None):
context = self.context
context = self.surface.context
position = self.position
size = self.size
gradient = RadialGradient.from_value(gradient)
@ -306,7 +373,7 @@ class WindowSurface(object):
context.restore()
def render_linear_gradient(self, gradient):
context = self.context
context = self.surface.context
position = self.position
size = self.size
gradient = Gradient.from_value(gradient)
@ -331,7 +398,7 @@ class WindowSurface(object):
corner_radius = BorderRadius.from_value(corner_radius)
gradient = Gradient.from_value(gradient)
context = self.context
context = self.surface.context
radius = corner_radius
degrees = math.pi / 180.0
x = position.x + self.position.x
@ -341,19 +408,7 @@ class WindowSurface(object):
if clip: #clips the entire region so any child windows will be confined to the parent
context.new_path()
context.arc(x + width - radius.topright - line_width/2.0,
y + radius.topright + line_width/2.0,
radius.topright+self.border_width/2.0, -90 * degrees, 0 * degrees)
context.arc(x + width - radius.bottomright - line_width/2.0,
y + height - radius.bottomright - line_width/2.0,
radius.bottomright+self.border_width/2.0, 0 * degrees, 90 * degrees)
context.arc(x + radius.bottomleft + line_width/2.0,
y + height - radius.bottomleft - line_width/2.0,
radius.bottomleft+self.border_width/2.0, 90 * degrees, 180 * degrees)
context.arc(x + radius.topleft + line_width/2.0,
y + radius.topleft + line_width/2.0,
radius.topleft+self.border_width/2.0, 180 * degrees, 270 * degrees)
context.close_path()
context.rectangle(x, y, width, height)
context.clip()
context.new_path()
@ -414,11 +469,11 @@ class WindowSurface(object):
def draw(self):
if self.visible:
self.context.save()
self.surface.context.save()
self.render()
for child in self.children:
child.draw()
self.context.restore()
self.surface.context.restore()
class Window(WindowEventSource, WindowSurface):
@ -432,7 +487,7 @@ class Window(WindowEventSource, WindowSurface):
position = Position.from_value(kwargs.pop('position', Position()))
size = Size.from_value(kwargs.pop('size', Size()))
self._context = kwargs.pop('context', None)
self._surface = kwargs.pop('surface', None)
self.min_size = Size.from_value(kwargs.pop('min_size', Size(1,1)))
self.max_size = Size.from_value(kwargs.pop('max_size', Size(-1,-1)))
self.corner_handle_size = Size.from_value(kwargs.pop('corner_handle_size', Size(20, 20)))
@ -472,7 +527,7 @@ class Window(WindowEventSource, WindowSurface):
self.accept('mouse-move', self.process_mouse_move)
self.size = size
self.position = position
self.context = self._context
self.surface = self._surface
self.resizable = kwargs.pop('resizable', self._resizable)
self.draggable = kwargs.pop('draggable', self._draggable)
@ -903,56 +958,10 @@ class Window(WindowEventSource, WindowSurface):
return self._root
def mouse_inside(self):
"""
Checks if the mouse is inside the window taking into account
all other windows and draw priorities.
"""
rec = self.rectangle
clip_parent = self.get_clip_parent()
if clip_parent is not None:
rec = self.rectangle.intersection(clip_parent.rectangle)
res = rec.intersects_with(self.mouse_pos)
if not res:
return False
#Check all children of root to see if there is a higher priority
#window than the current one
root = self.root
stack = [root]
visited = set()
while stack:
item = stack[-1]
rec = item.rectangle
clip_parent = item.get_clip_parent()
if clip_parent is not None:
rec = item.rectangle.intersection(clip_parent.rectangle)
intersects_mouse = rec.intersects_with(self.mouse_pos)
if item.children and not set(item.children).issubset(visited):
stack.extend(item.children)
else:
if item is self:
break
else:
res &= not intersects_mouse
visited.add(item)
stack.pop()
return res
return self is self.surface.current_hover_window
def mouse_held(self):
res = self.mouse_down
stack = [self.root]
visited = set()
while len(stack) > 0:
item = stack[-1]
if item.children and not set(item.children).issubset(visited):
stack.extend(item.children)
else:
if self is not item:
res |= item.mouse_down
visited.add(item)
stack.pop()
return res
return self.mouse_down
def process_mouse_move(self, obj, old_mpos, new_mpos):
mouse_focus = self.mouse_inside()
@ -970,14 +979,14 @@ class Window(WindowEventSource, WindowSurface):
self.mouse_in = False
@property
def context(self):
return self._context
def surface(self):
return self._surface
@context.setter
def context(self, context):
self._context = context
@surface.setter
def surface(self, surface):
self._surface = surface
for child in self.children:
child.context = self._context
child.surface = self._surface
def add_child(self, child_window):
if child_window not in self.children:
@ -985,7 +994,7 @@ class Window(WindowEventSource, WindowSurface):
child_window.position = child_window.position + self.position +\
[self.border_width/2, self.border_width/2] +\
[self.padding.left, self.padding.top]
child_window.context = self.context
child_window.surface = self.surface
self.children.append(child_window)
def remove_child(self, child_window):

11
test.py
View file

@ -11,22 +11,24 @@ from pandac.PandaModules import *
from testclasses import *
class Test(ShowBase):
def __init__(self, width=1024, height=1024):
def __init__(self, width=800, height=600):
ShowBase.__init__(self)
self._render = True
self.width = width
self.height = height
props = WindowProperties()
props.setCursorHidden(True)
props.setSize(width, height)
base.setBackgroundColor(0.5,1,1,1)
base.cam.setPos(0,-3.7,0)
base.cam.setPos(0,-10,0)
base.win.requestProperties(props)
base.disableMouse()
c = CardMaker('plane')
c.setFrame(-1, 1, 1, -1)
c.setHasUvs(True)
screen = render2d.attachNewNode(c.generate())
screen.setTransparency(TransparencyAttrib.MAlpha)
screen.setPos(0,0,0)
base.buttonThrowers[0].node().setMoveEvent('mouse_move')
self.surf = TestSurface([width, height])
@ -76,7 +78,8 @@ class Test(ShowBase):
def drawall(self, task):
if self._render:
self.surf.draw()
self.cairoTexture.setRamImage(self.surf.csurface.get_data())
ri = self.cairoTexture.modifyRamImage()
ri.setData(self.surf.csurface.get_data())
return task.cont
def mousemove(self, task):

View file

@ -5,6 +5,7 @@ from jgui.settings import IMG_DIR
class TestSurface(Surface):
def __init__(self, *args, **kwargs):
super(TestSurface, self).__init__(*args, **kwargs)
self.show_fps = True
my_win = Window('child',
position=[0,0],
size=[500,200],
@ -16,8 +17,8 @@ class TestSurface(Surface):
border_radius=[40,20],
border_color=(0,0,0),
background_color=(1,1,1),
clip_children=True,
ignore_debug=True,
clip_children=True,
background_image=os.path.join(IMG_DIR, 'wrench.png'),
background_image_filter='bilinear',
background_image_keep_ratio=True,
@ -52,7 +53,7 @@ class TestSurface(Surface):
background_image_keep_ratio=True,
background_image_center_vertical=False,
background_image_center_horizontal=False,
gradient=Gradient(stops=[(0, Color(1,1,1)), (0.8, Color(0.5,0.5,0.5)), (1, Color(0.7,0.7,0.7))]))
gradient=Gradient(stops=[(0, Color(1,1,1)), (0.8, Color(0.5,0.5,0.5, 0.5)), (1, Color(0.7,0.7,0.7,0.5))]))
my_win2.add_child(TextWindow('text2',"I haven't had a bean in forever.",
position=[20,100], size=[200, 100],