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 __future__ import print_function
from jgui.settings import DEBUG import jgui.settings as settings
def log(*args): def log(*args):
if DEBUG: if settings.DEBUG:
print(*args) print(*args)

View file

@ -6,22 +6,41 @@ import math
from .structures import Size, Position, Rectangle, Color, BorderRadius, Padding, Gradient, RadialGradient from .structures import Size, Position, Rectangle, Color, BorderRadius, Padding, Gradient, RadialGradient
from ..events.events import WindowEventSource from ..events.events import WindowEventSource
from ..logger import log from ..logger import log
from datetime import datetime
from jgui.settings import DEBUG as debug
debug = True
class Surface(object): class Surface(WindowEventSource):
def __init__(self, size=None, context=None, render_mouse=True): 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.size = Size.from_value(size)
self.mouse_pos = Position()
if context is None: 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) self.context = cairo.Context(self.csurface)
else: else:
self.context = context 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 self.render_mouse = render_mouse
if self.render_mouse: if self.render_mouse:
self.mouse_icon = Mouse('mouse', position=Position(0, 0), size=Size(12,20), context=self.context) self.mouse_icon = Mouse('mouse', position=Position(0, 0), size=Size(12,20), context=self.context, surface=self)
self.windows = [self.root_window, self.mouse_icon] 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): def setTopZero(self, context):
context.identity_matrix() context.identity_matrix()
@ -29,17 +48,57 @@ class Surface(object):
1, 0, 0) 1, 0, 0)
context.transform(matrix) 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): def inject_mouse_down(self, button):
self.current_focused_window = self.current_hover_window
self.root_window.inject_mouse_down(button) self.root_window.inject_mouse_down(button)
def inject_mouse_up(self, button): def inject_mouse_up(self, button):
self.root_window.inject_mouse_up(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): def notify_window_resize(self, width, height):
self.size = Size(width, height) self.size = Size(width, height)
self.root_window.size = self.size self.root_window.size = self.size
@ -53,9 +112,21 @@ class Surface(object):
if item.children: if item.children:
stack.extend(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): def draw(self):
#self.setTopZero(ctx) if self.show_fps:
#self.context.identity_matrix() 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.set_operator(cairo.OPERATOR_CLEAR)
self.context.rectangle(0.0, 0.0, self.size.width, self.size.height) self.context.rectangle(0.0, 0.0, self.size.width, self.size.height)
self.context.fill() self.context.fill()
@ -64,7 +135,6 @@ class Surface(object):
window.draw() window.draw()
class WindowSurface(object): class WindowSurface(object):
line_joins = {'miter': cairo.LINE_JOIN_MITER, 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): 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) color = Color.from_value(color)
line_color = Color.from_value(line_color) line_color = Color.from_value(line_color)
context = self.context context = self.surface.context
position = Position.from_value(position) position = Position.from_value(position)
size = Size.from_value(size) size = Size.from_value(size)
width = size.width width = size.width
@ -148,7 +218,7 @@ class WindowSurface(object):
center_horizontal=True, center_horizontal=True,
center_vertical=True, image_offset=(0, 0)): center_vertical=True, image_offset=(0, 0)):
context = self.context context = self.surface.context
position = Position.from_value(position) position = Position.from_value(position)
offset = Position.from_value(image_offset) offset = Position.from_value(image_offset)
size = Size.from_value(size) size = Size.from_value(size)
@ -158,9 +228,6 @@ class WindowSurface(object):
x,y = (self.position.x+position.x, x,y = (self.position.x+position.x,
self.position.y+position.y) self.position.y+position.y)
context.rectangle(x, y, width, height)
context.clip()
if isinstance(image, basestring): if isinstance(image, basestring):
image = self.load_image(image) image = self.load_image(image)
@ -229,7 +296,7 @@ class WindowSurface(object):
color = Color.from_value(font_color) color = Color.from_value(font_color)
background_color = Color.from_value(background_color) background_color = Color.from_value(background_color)
position = Position.from_value(position) position = Position.from_value(position)
context = self.context context = self.surface.context
font_weight = self.font_weights[font_weight] font_weight = self.font_weights[font_weight]
font_style = self.font_styles[font_style] font_style = self.font_styles[font_style]
@ -264,7 +331,7 @@ class WindowSurface(object):
if lines: if lines:
line_color = Color.from_value(line_color) line_color = Color.from_value(line_color)
background_color = Color.from_value(background_color) background_color = Color.from_value(background_color)
context = self.context context = self.surface.context
start_pos = self.position + lines[0] start_pos = self.position + lines[0]
context.set_line_width(line_width) context.set_line_width(line_width)
context.move_to(start_pos.x, start_pos.y) context.move_to(start_pos.x, start_pos.y)
@ -286,7 +353,7 @@ class WindowSurface(object):
context.stroke() context.stroke()
def render_radial_gradient(self, gradient, inner_radius=None, outer_radius=None): def render_radial_gradient(self, gradient, inner_radius=None, outer_radius=None):
context = self.context context = self.surface.context
position = self.position position = self.position
size = self.size size = self.size
gradient = RadialGradient.from_value(gradient) gradient = RadialGradient.from_value(gradient)
@ -306,7 +373,7 @@ class WindowSurface(object):
context.restore() context.restore()
def render_linear_gradient(self, gradient): def render_linear_gradient(self, gradient):
context = self.context context = self.surface.context
position = self.position position = self.position
size = self.size size = self.size
gradient = Gradient.from_value(gradient) gradient = Gradient.from_value(gradient)
@ -331,7 +398,7 @@ class WindowSurface(object):
corner_radius = BorderRadius.from_value(corner_radius) corner_radius = BorderRadius.from_value(corner_radius)
gradient = Gradient.from_value(gradient) gradient = Gradient.from_value(gradient)
context = self.context context = self.surface.context
radius = corner_radius radius = corner_radius
degrees = math.pi / 180.0 degrees = math.pi / 180.0
x = position.x + self.position.x 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 if clip: #clips the entire region so any child windows will be confined to the parent
context.new_path() context.new_path()
context.arc(x + width - radius.topright - line_width/2.0, context.rectangle(x, y, width, height)
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.clip() context.clip()
context.new_path() context.new_path()
@ -414,11 +469,11 @@ class WindowSurface(object):
def draw(self): def draw(self):
if self.visible: if self.visible:
self.context.save() self.surface.context.save()
self.render() self.render()
for child in self.children: for child in self.children:
child.draw() child.draw()
self.context.restore() self.surface.context.restore()
class Window(WindowEventSource, WindowSurface): class Window(WindowEventSource, WindowSurface):
@ -432,7 +487,7 @@ class Window(WindowEventSource, WindowSurface):
position = Position.from_value(kwargs.pop('position', Position())) position = Position.from_value(kwargs.pop('position', Position()))
size = Size.from_value(kwargs.pop('size', Size())) 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.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.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))) 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.accept('mouse-move', self.process_mouse_move)
self.size = size self.size = size
self.position = position self.position = position
self.context = self._context self.surface = self._surface
self.resizable = kwargs.pop('resizable', self._resizable) self.resizable = kwargs.pop('resizable', self._resizable)
self.draggable = kwargs.pop('draggable', self._draggable) self.draggable = kwargs.pop('draggable', self._draggable)
@ -903,56 +958,10 @@ class Window(WindowEventSource, WindowSurface):
return self._root return self._root
def mouse_inside(self): def mouse_inside(self):
""" return self is self.surface.current_hover_window
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
def mouse_held(self): def mouse_held(self):
res = self.mouse_down return 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
def process_mouse_move(self, obj, old_mpos, new_mpos): def process_mouse_move(self, obj, old_mpos, new_mpos):
mouse_focus = self.mouse_inside() mouse_focus = self.mouse_inside()
@ -970,14 +979,14 @@ class Window(WindowEventSource, WindowSurface):
self.mouse_in = False self.mouse_in = False
@property @property
def context(self): def surface(self):
return self._context return self._surface
@context.setter @surface.setter
def context(self, context): def surface(self, surface):
self._context = context self._surface = surface
for child in self.children: for child in self.children:
child.context = self._context child.surface = self._surface
def add_child(self, child_window): def add_child(self, child_window):
if child_window not in self.children: if child_window not in self.children:
@ -985,7 +994,7 @@ class Window(WindowEventSource, WindowSurface):
child_window.position = child_window.position + self.position +\ child_window.position = child_window.position + self.position +\
[self.border_width/2, self.border_width/2] +\ [self.border_width/2, self.border_width/2] +\
[self.padding.left, self.padding.top] [self.padding.left, self.padding.top]
child_window.context = self.context child_window.surface = self.surface
self.children.append(child_window) self.children.append(child_window)
def remove_child(self, child_window): def remove_child(self, child_window):

11
test.py
View file

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

View file

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