summaryrefslogtreecommitdiff
path: root/unrpyc/renpy/display/render.pyx
diff options
context:
space:
mode:
Diffstat (limited to 'unrpyc/renpy/display/render.pyx')
-rw-r--r--unrpyc/renpy/display/render.pyx1174
1 files changed, 1174 insertions, 0 deletions
diff --git a/unrpyc/renpy/display/render.pyx b/unrpyc/renpy/display/render.pyx
new file mode 100644
index 0000000..5aa45a0
--- /dev/null
+++ b/unrpyc/renpy/display/render.pyx
@@ -0,0 +1,1174 @@
+#cython: profile=False
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import collections
+import pygame
+import threading
+import renpy
+import gc
+
+# We grab the blit lock each time it is necessary to blit
+# something. This allows call to the pygame.transform functions to
+# disable blitting, should it prove necessary.
+blit_lock = threading.Condition()
+
+# This is a dictionary containing all the renders that we know of. It's a
+# map from displayable to dictionaries containing the render of that
+# displayable.
+render_cache = collections.defaultdict(dict)
+
+# The queue of redraws. A list of (time, displayable) pairs.
+redraw_queue = [ ]
+
+# The render returned from render_screen.
+screen_render = None
+
+# A list of renders the system knows about, and thinks are still alive.
+cdef list live_renders
+live_renders = [ ]
+
+# A copy of renpy.display.interface.frame_time, for speed reasons.
+cdef double frame_time
+frame_time = 0
+
+def free_memory():
+ """
+ Frees memory used by the render system.
+ """
+
+ global screen_render
+ screen_render = None
+
+ mark_sweep()
+
+ render_cache.clear()
+
+ # This can hang onto a render.
+ renpy.display.interface.surftree = None
+
+
+def check_at_shutdown():
+ """
+ This is called at shutdown time to check that everything went okay.
+ The big thing it checks for is memory leaks.
+ """
+
+ if not renpy.config.developer:
+ return
+
+ free_memory()
+
+ gc.collect()
+ l = gc.get_objects()
+
+ count = 0
+ objects = gc.get_objects()
+
+ for i in objects:
+ if isinstance(i, Render):
+ count += 1
+
+ if count:
+ raise Exception("%d Renders are alive at shutdown. This is probably a memory leak bug in Ren'Py." % count)
+
+
+
+cpdef render(d, object widtho, object heighto, double st, double at):
+ """
+ :doc: udd_utility
+ :args: (d, width, height, st, at)
+
+ Causes a displayable to be rendered, and a renpy.Render object to
+ be returned.
+
+ `d`
+ The displayable to render.
+
+ `width`, `height`
+ The width and height available for the displayable to render into.
+
+ `st`, `at`
+ The shown and animation timebases.
+
+ Renders returned by this object may be cached, and should not be modified
+ once they have been retrieved.
+ """
+
+ cdef float width, height
+ cdef float orig_width, orig_height
+ cdef tuple orig_wh, wh
+ cdef dict render_cache_d
+ cdef Render rv
+
+ orig_wh = (widtho, heighto, frame_time-st, frame_time-at)
+
+ render_cache_d = render_cache[d]
+ rv = render_cache_d.get(orig_wh, None)
+
+ if rv is not None:
+ return rv
+
+ orig_width = width = widtho
+ orig_height = height = heighto
+
+ style = d.style
+ xmaximum = style.xmaximum
+ ymaximum = style.ymaximum
+
+ if xmaximum is not None:
+ if isinstance(xmaximum, float):
+ width = width * xmaximum
+ else:
+ width = min(xmaximum, width)
+
+ if ymaximum is not None:
+ if isinstance(ymaximum, float):
+ height = height * ymaximum
+ else:
+ height = min(ymaximum, height)
+
+ if width < 0:
+ width = 0
+ if height < 0:
+ height = 0
+
+ if orig_width != width or orig_height != height:
+ widtho = width
+ heighto = height
+ wh = (widtho, heighto, frame_time-st, frame_time-at)
+ rv = render_cache_d.get(wh, None)
+
+ if rv is not None:
+ return rv
+
+ else:
+ wh = orig_wh
+
+ rv = d.render(widtho, heighto, st, at)
+
+ rv.render_of.append(d)
+
+ if style.clipping:
+ rv = rv.subsurface((0, 0, rv.width, rv.height), focus=True)
+ rv.render_of.append(d)
+
+ render_cache_d[wh] = rv
+
+ if wh is not orig_wh:
+ render_cache_d[orig_wh] = rv
+
+ return rv
+
+
+# This is true if something has been invalidated, and a redraw needs
+# to occur. It's automatically cleared to False at the end of each
+# redraw.
+invalidated = False
+
+def invalidate(d):
+ """
+ Removes d from the render cache. If we're not in a redraw, triggers
+ a redraw to start.
+ """
+
+ global invalidated
+
+ if d in render_cache:
+ for v in render_cache[d].values():
+ v.kill_cache()
+
+ invalidated = True
+
+
+def process_redraws():
+ """
+ Called to determine if any redraws are pending. Returns true if we
+ need to redraw the screen now, false otherwise.
+ """
+
+ global redraw_queue
+
+ redraw_queue.sort()
+
+ now = renpy.display.core.get_time()
+ rv = invalidated
+
+ new_redraw_queue = [ ]
+ seen = set()
+
+ for t in redraw_queue:
+ when, d = t
+
+ if d in seen:
+ continue
+
+ seen.add(d)
+
+ if d not in render_cache:
+ continue
+
+ if when <= now:
+ # Remove this displayable and all its parents from the
+ # render cache. But don't kill them yet, as that will kill the
+ # children that we want to reuse.
+
+ for v in render_cache[d].values():
+ v.kill_cache()
+
+ rv = True
+
+ else:
+ new_redraw_queue.append(t)
+
+ redraw_queue = new_redraw_queue
+
+ return rv
+
+
+def redraw_time():
+ """
+ Returns the time at which the next redraw is scheduled.
+ """
+
+ if redraw_queue:
+ return redraw_queue[0][0]
+
+ return None
+
+
+def redraw(d, when):
+ """
+ :doc: udd_utility
+
+ Causes the displayable `d` to be redrawn after `when` seconds have
+ elapsed.
+ """
+
+ if not renpy.game.interface:
+ return
+
+ redraw_queue.append((when + renpy.game.interface.frame_time, d))
+
+
+cdef class Matrix2D:
+ """
+ This represents a 2d matrix that can be used to transform
+ points and things like that.
+ """
+
+ def __getstate__(self):
+ return dict(
+ xdx = self.xdx,
+ xdy = self.xdy,
+ ydx = self.ydx,
+ ydy = self.ydy)
+
+ def __setstate__(self, state):
+ self.xdx = state['xdx']
+ self.xdy = state['xdy']
+ self.ydx = state['ydx']
+ self.ydy = state['ydy']
+
+ def __init__(Matrix2D self, double xdx, double xdy, double ydx, double ydy):
+ self.xdx = xdx
+ self.xdy = xdy
+ self.ydx = ydx
+ self.ydy = ydy
+
+ cpdef tuple transform(Matrix2D self, double x, double y):
+ return (x * self.xdx + y * self.xdy), (x * self.ydx + y * self.ydy)
+
+ def __mul__(Matrix2D self, Matrix2D other):
+ return Matrix2D(
+ other.xdx * self.xdx + other.xdy * self.ydx,
+ other.xdx * self.xdy + other.xdy * self.ydy,
+ other.ydx * self.xdx + other.ydy * self.ydx,
+ other.ydx * self.xdy + other.ydy * self.ydy)
+
+ def __repr__(self):
+ return "Matrix2D(xdx=%f, xdy=%f, ydx=%f, ydy=%f)" % (self.xdx, self.xdy, self.ydx, self.ydy)
+
+IDENTITY = Matrix2D(1, 0, 0, 1)
+
+def take_focuses(focuses):
+ """
+ Adds a list of rectangular focus regions to the focuses list.
+ """
+
+ screen_render.take_focuses(
+ 0, 0, screen_render.width, screen_render.height,
+ IDENTITY, 0, 0, focuses)
+
+# The result of focus_at_point for a modal render. This overrides any
+# specific focus from below us.
+Modal = object()
+
+def focus_at_point(x, y):
+ """
+ Returns a focus object corresponding to the uppermost displayable
+ at point, or None if nothing focusable is at point.
+ """
+
+ if screen_render is None:
+ return None
+
+ cf = screen_render.focus_at_point(x, y)
+ if cf is None or cf is Modal:
+ return None
+ else:
+ d, arg = cf
+ return renpy.display.focus.Focus(d, arg, None, None, None, None)
+
+
+def mutated_surface(surf):
+ """
+ Called to indicate that the given surface has changed.
+ """
+
+ renpy.display.draw.mutated_surface(surf)
+
+
+def render_screen(root, width, height):
+ """
+ Renders `root` (a displayable) as the root of a screen with the given
+ `width` and `height`.
+ """
+
+
+
+ global old_screen_render
+ global screen_render
+ global invalidated
+ global frame_time
+
+ frame_time = renpy.display.interface.frame_time
+
+ rv = render(root, width, height, 0, 0)
+ screen_render = rv
+
+ invalidated = False
+
+ rv.is_opaque()
+
+ return rv
+
+def mark_sweep():
+ """
+ This performs mark-and-sweep garbage collection on the live_renders
+ list.
+ """
+
+ global live_renders
+
+ cdef list worklist
+ cdef int i
+ cdef Render r, j
+
+ worklist = [ ]
+
+ if screen_render is not None:
+ worklist.append(screen_render)
+
+ i = 0
+
+ while i < len(worklist):
+ r = worklist[i]
+
+ for j in r.depends_on_list:
+ if not j.mark:
+ j.mark = True
+ worklist.append(j)
+
+ i += 1
+
+ for r in live_renders:
+ if not r.mark:
+ r.kill_cache()
+ else:
+ r.mark = False
+
+ live_renders = worklist
+
+def compute_subline(sx0, sw, cx0, cw):
+ """
+ Given a source line (start sx0, width sw) and a crop line (cx0, cw),
+ return three things:
+
+ * The offset of the portion of the source line that overlaps with
+ the crop line, relative to the crop line.
+ * The offset of the portion of the source line that overlaps with the
+ the crop line, relative to the source line.
+ * The length of the overlap in pixels. (can be <= 0)
+ """
+
+ sx1 = sx0 + sw
+ cx1 = cx0 + cw
+
+ if sx0 > cx0:
+ start = sx0
+ else:
+ start = cx0
+
+ offset = start - cx0
+ crop = start - sx0
+
+ if sx1 < cx1:
+ width = sx1 - start
+ else:
+ width = cx1 - start
+
+
+ return offset, crop, width
+
+
+
+
+# Possible operations that can be done as part of a render.
+BLIT = 0
+DISSOLVE = 1
+IMAGEDISSOLVE = 2
+PIXELLATE = 3
+
+cdef class Render:
+
+ def __init__(Render self, float width, float height, draw_func=None, layer_name=None, bint opaque=None): #@DuplicatedSignature
+ """
+ Creates a new render corresponding to the given widget with
+ the specified width and height.
+
+ If `layer_name` is given, then this render corresponds to a
+ layer.
+ """
+
+ # The mark bit, used for mark/sweep-style garbage collection of
+ # renders.
+ self.mark = False
+
+ # Is has this render been removed from the cache?
+ self.cache_killed = False
+
+
+ self.width = width
+ self.height = height
+
+ self.layer_name = layer_name
+
+ # A list of (surface/render, xoffset, yoffset, focus, main) tuples, ordered from
+ # back to front.
+ self.children = [ ]
+
+ # The set of renders that either have us as children, or depend on
+ # us.
+ self.parents = set()
+
+ # The renders we depend on, including our children.
+ self.depends_on_list = [ ]
+
+ # The operation we're performing. (BLIT, DISSOLVE, OR IMAGE_DISSOLVE)
+ self.operation = BLIT
+
+ # The fraction of the operation that is complete.
+ self.operation_complete = 0.0
+
+ # Should the dissolve operations preserve alpha?
+ self.operation_alpha = False
+
+ # The parameter to the operation.
+ self.operation_parameter = 0
+
+ # Forward is used to transform from screen coordinates to child
+ # coordinates.
+ # Reverse is used to transform from child coordinates to screen
+ # coordinates.
+ #
+ # For performance reasons, these aren't used to transform the
+ # x and y offsets found in self.children. Those offsets should
+ # be of the (0, 0) point in the child coordinate space.
+ self.forward = None
+ self.reverse = None
+
+ # This is used to adjust the alpha of children of this render.
+ self.alpha = 1
+
+ # A list of focus regions in this displayable.
+ self.focuses = None
+
+ # Other renders that we should pass focus onto.
+ self.pass_focuses = None
+
+ # The displayable(s) that this is a render of. (Set by render)
+ self.render_of = [ ]
+
+ # If set, this is a function that's called to draw this render
+ # instead of the default.
+ self.draw_func = draw_func
+
+ # Is this displayable opaque? (May be set on init, or later on
+ # if we have opaque children.) This may be True, False, or None
+ # to indicate we don't know yet.
+ self.opaque = opaque
+
+ # A list of our visible children. (That is, children above and
+ # including our uppermost opaque child.) If nothing is opaque,
+ # includes all children.
+ self.visible_children = self.children
+
+ # Should children be clipped to a rectangle?
+ self.clipping = False
+
+ # Caches of the texture created by rendering this surface.
+ self.surface = None
+ self.alpha_surface = None
+
+ # Cache of the texture created by rendering this surface at half size.
+ # (This is set in gldraw.)
+ self.half_cache = None
+
+ # Are we modal?
+ self.modal = False
+
+ live_renders.append(self)
+
+ def __repr__(self): #@DuplicatedSignature
+ return "<Render %x of %r>" % (id(self), self.render_of)
+
+ def __getstate__(self): #@DuplicatedSignature
+ if renpy.config.developer:
+ raise Exception("Can't pickle a Render.")
+ else:
+ return { }
+
+ def __setstate__(self, state): #@DuplicatedSignature
+ return
+
+ cpdef int blit(Render self, source, tuple pos, object focus=True, object main=True, object index=None):
+ """
+ Blits `source` (a Render or Surface) to this Render, offset by
+ xo and yo.
+
+ If `focus` is true, then focuses are added from the child to the
+ parent.
+
+ This will only blit on integer pixel boundaries.
+ """
+
+ (xo, yo) = pos
+
+ if source is self:
+ raise Exception("Blitting to self.")
+
+ xo = int(xo)
+ yo = int(yo)
+
+ if index is None:
+ self.children.append((source, xo, yo, focus, main))
+ else:
+ self.children.insert(index, (source, xo, yo, focus, main))
+
+ if isinstance(source, Render):
+ self.depends_on_list.append(source)
+ source.parents.add(self)
+
+ return 0
+
+ cpdef int subpixel_blit(Render self, source, tuple pos, object focus=True, object main=True, object index=None):
+ """
+ Blits `source` (a Render or Surface) to this Render, offset by
+ xo and yo.
+
+ If `focus` is true, then focuses are added from the child to the
+ parent.
+
+ This blits at fractional pixel boundaries.
+ """
+
+ (xo, yo) = pos
+
+ xo = float(xo)
+ yo = float(yo)
+
+ if index is None:
+ self.children.append((source, xo, yo, focus, main))
+ else:
+ self.children.insert(index, (source, xo, yo, focus, main))
+
+ if isinstance(source, Render):
+ self.depends_on_list.append(source)
+ source.parents.add(self)
+
+ return 0
+
+ def get_size(self):
+ """
+ Returns the size of this Render, a mostly ficticious value
+ that's taken from the inputs to the constructor. (As in, we
+ don't clip to this size.)
+ """
+
+ return self.width, self.height
+
+
+ def render_to_texture(self, alpha=True):
+ """
+ Returns a texture constructed from this render. This may return
+ a cached textue, if one has already been rendered.
+
+ `alpha` is a hint that controls if the surface should have
+ alpha or not.
+ """
+
+ if alpha:
+ if self.alpha_surface is not None:
+ return self.alpha_surface
+ else:
+ if self.surface is not None:
+ return self.surface
+
+ rv = None
+
+ opaque = self.is_opaque()
+
+ # If we can, reuse a child's texture.
+ if opaque or alpha:
+
+ if not self.forward and len(self.children) == 1:
+ child, x, y, focus, main = self.children[0]
+ cw, ch = child.get_size()
+ if x <= 0 and y <= 0 and cw + x >= self.width and ch + y >= self.height:
+ # Our single child overlaps us.
+ if isinstance(child, Render):
+ child = child.render_to_texture(alpha)
+
+ if x != 0 or y != 0 or cw != self.width or ch != self.height:
+ rv = child.subsurface((-x, -y, self.width, self.height))
+ else:
+ rv = child
+
+ # Otherwise, render to a texture.
+ if rv is None:
+ # is_opaque has already been called.
+ rv = renpy.display.draw.render_to_texture(self, alpha)
+
+ # Stash and return the surface.
+ if alpha:
+ self.alpha_surface = rv
+ else:
+ self.surface = rv
+
+ return rv
+
+ pygame_surface = render_to_texture
+
+ def subsurface(self, rect, focus=False):
+ """
+ Returns a subsurface of this render. If `focus` is true, then
+ the focuses are copied from this render to the child.
+ """
+
+ (x, y, w, h) = rect
+ rv = Render(w, h)
+
+ reverse = self.reverse
+
+ # This doesn't actually make a subsurface, as we can't easily do
+ # so for non-rectangle-aligned renders.
+ if (reverse is not None) and (
+ reverse.xdx != 1.0 or
+ reverse.xdy != 0.0 or
+ reverse.ydx != 0.0 or
+ reverse.ydy != 1.0):
+
+ rv.clipping = True
+ rv.blit(self, (-x, -y), focus=focus, main=True)
+ return rv
+
+ # This is the path that executes for rectangle-aligned surfaces,
+ # making an actual subsurface.
+
+ for child, cx, cy, cfocus, cmain in self.children:
+
+ cw, ch = child.get_size()
+ xo, cx, cw = compute_subline(cx, cw, x, w)
+ yo, cy, ch = compute_subline(cy, ch, y, h)
+
+ if cw <= 0 or ch <= 0:
+ continue
+
+ crop = (cx, cy, cw, ch)
+ offset = (xo, yo)
+
+ if isinstance(child, Render):
+ newchild = child.subsurface(crop, focus=focus)
+ newchild.render_of = child.render_of[:]
+ else:
+ newchild = child.subsurface(crop)
+ renpy.display.draw.mutated_surface(newchild)
+
+ rv.blit(newchild, offset, focus=cfocus, main=cmain)
+
+ if focus and self.focuses:
+
+ for (d, arg, xo, yo, fw, fh, mx, my, mask) in self.focuses:
+
+ if xo is None:
+ rv.add_focus(d, arg, xo, yo, fw, fh, mx, my, mask)
+ continue
+
+ xo, cx, fw = compute_subline(xo, fw, x, w)
+ yo, cy, fh = compute_subline(yo, fh, y, h)
+
+ if cw <= 0 or ch <= 0:
+ continue
+
+ if mx is not None:
+
+ mw, mh = mask.get_size()
+
+ mx, mcx, mw = compute_subline(mx, mw, x, w)
+ my, mcy, mh = compute_subline(my, mh, y, h)
+
+ if mw <= 0 or mh <= 0:
+ mx = None
+ my = None
+ mask = None
+ else:
+ mask = mask.subsurface((mcx, mcy, mw, mh))
+
+ rv.add_focus(d, arg, xo, yo, fw, fh, mx, my, mask)
+
+ rv.depends_on(self)
+ rv.alpha = self.alpha
+ rv.operation = self.operation
+ rv.operation_alpha = self.operation_alpha
+ rv.operation_complete = self.operation_complete
+
+ return rv
+
+
+ def depends_on(self, source, focus=False):
+ """
+ Used to indicate that this render depends on another
+ render. Useful, for example, if we use pygame_surface to make
+ a surface, and then blit that surface into another render.
+ """
+
+ if source is self:
+ raise Exception("Render depends on itself.")
+
+ self.depends_on_list.append(source)
+ source.parents.add(self)
+
+ if focus:
+ if self.pass_focuses is None:
+ self.pass_focuses = [ source ]
+ else:
+ self.pass_focuses.append(source)
+
+
+ def kill_cache(self):
+ """
+ Removes this render and its transitive parents from the cache.
+ """
+
+ if self.cache_killed:
+ return
+
+ self.cache_killed = True
+
+ for i in self.parents:
+ i.kill_cache()
+
+ self.parents.clear()
+
+ for i in self.depends_on_list:
+ if not i.cache_killed:
+ i.parents.discard(self)
+
+ for ro in self.render_of:
+ cache = render_cache[ro]
+ for k, v in cache.items():
+ if v is self:
+ del cache[k]
+
+ if not cache:
+ del render_cache[ro]
+
+ def kill(self):
+ """
+ Retained for compatibility.
+ """
+
+ def add_focus(self, d, arg=None, x=0, y=0, w=None, h=None, mx=None, my=None, mask=None):
+ """
+ This is called to indicate a region of the screen that can be
+ focused.
+
+ `d` - the displayable that is being focused.
+ `arg` - an argument.
+
+ The rest of the parameters are a rectangle giving the portion of
+ this region corresponding to the focus. If they are all None, than
+ this focus is assumed to be the singular full-screen focus.
+ """
+
+ if mask is not None and mask is not self:
+ self.depends_on(mask)
+
+ t = (d, arg, x, y, w, h, mx, my, mask)
+
+ if self.focuses is None:
+ self.focuses = [ t ]
+ else:
+ self.focuses.append(t)
+
+ def take_focuses(self, cminx, cminy, cmaxx, cmaxy, reverse, x, y, focuses): #@DuplicatedSignature
+ """
+ This adds to focuses Focus objects corresponding to the focuses
+ added to this object and its children, transformed into screen
+ coordinates.
+
+ `cminx`, `cminy`, `cmaxx`, `cmaxy` - The clipping rectangle.
+ `reverse` - The transform from render to screen coordinates.
+ `x`, `y` - The offset of the upper-left corner of the render.
+ `focuses` - The list of focuses to add to.
+ """
+
+ if self.modal:
+ focuses[:] = [ ]
+
+ if self.reverse:
+ reverse = reverse * self.reverse
+
+ if self.focuses:
+
+ for (d, arg, xo, yo, w, h, mx, my, mask) in self.focuses:
+
+ if xo is None:
+ focuses.append(renpy.display.focus.Focus(d, arg, None, None, None, None))
+ continue
+
+ x1, y1 = reverse.transform(xo, yo)
+ x2, y2 = reverse.transform(xo + w, yo + h)
+
+ minx = min(x1, x2) + x
+ miny = min(y1, y2) + y
+ maxx = max(x1, x2) + x
+ maxy = max(y1, y2) + y
+
+ minx = max(minx, cminx)
+ miny = max(miny, cminy)
+ maxx = min(maxx, cmaxx)
+ maxy = min(maxy, cmaxy)
+
+ if minx >= maxx or miny >= maxy:
+ continue
+
+ focuses.append(renpy.display.focus.Focus(d, arg, minx, miny, maxx - minx, maxy - miny))
+
+ if self.clipping:
+ cminx = max(cminx, x)
+ cminy = max(cminy, y)
+ cmaxx = min(cmaxx, x + self.width)
+ cmaxy = min(cmaxx, x + self.height)
+
+ for child, xo, yo, focus, main in self.children:
+ if not focus or not isinstance(child, Render):
+ continue
+
+ xo, yo = reverse.transform(xo, yo)
+ child.take_focuses(cminx, cminy, cmaxx, cmaxy, reverse, x + xo, y + yo, focuses)
+
+ if self.pass_focuses:
+ for child in self.pass_focuses:
+ child.take_focuses(cminx, cminy, cmaxx, cmaxy, reverse, x, y, focuses)
+
+ def focus_at_point(self, x, y): #@DuplicatedSignature
+ """
+ This returns the focus of this object at the given point.
+ """
+
+ if self.clipping:
+ if x < 0 or x >= self.width or y < 0 or y >= self.height:
+ return None
+
+ rv = None
+
+ if self.focuses:
+ for (d, arg, xo, yo, w, h, mx, my, mask) in self.focuses:
+
+ if xo is None:
+ continue
+
+ elif mx is not None:
+ cx = x - mx
+ cy = y - my
+
+ if self.forward:
+ cx, cy = self.forward.transform(cx, cy)
+
+ if mask.is_pixel_opaque(cx, cy):
+ rv = d, arg
+
+ elif xo <= x < xo + w and yo <= y < yo + h:
+ rv = d, arg
+
+ for child, xo, yo, focus, main in self.children:
+
+ if not focus or not isinstance(child, Render):
+ continue
+
+ cx = x - xo
+ cy = y - yo
+
+ if self.forward:
+ cx, cy = self.forward.transform(cx, cy)
+
+ cf = child.focus_at_point(cx, cy)
+ if cf is not None:
+ rv = cf
+
+ if self.pass_focuses:
+ for child in self.pass_focuses:
+ cf = child.focus_at_point(x, y)
+ if cf is not None:
+ rv = cf
+
+ if rv is None and self.modal:
+ rv = Modal
+
+ return rv
+
+
+ def main_displayables_at_point(self, x, y, layers, depth=None):
+ """
+ Returns the displayable at `x`, `y` on one of the layers in
+ the set or list `layers`.
+ """
+
+ rv = [ ]
+
+ if x < 0 or y < 0 or x >= self.width or y >= self.height:
+ return rv
+
+ if depth is not None:
+ for d in self.render_of:
+ rv.append((depth, self.width, self.height, d))
+ depth += 1
+ elif self.layer_name in layers:
+ depth = 0
+
+ for (child, xo, yo, focus, main) in self.children:
+ if not main or not isinstance(child, Render):
+ continue
+
+ cx = x - xo
+ cy = y - yo
+
+ if self.forward:
+ cx, cy = self.forward.transform(cx, cy)
+
+ cf = child.main_displayables_at_point(cx, cy, layers, depth)
+ rv.extend(cf)
+
+ return rv
+
+
+ def is_opaque(self):
+ """
+ Returns true if this displayable is opaque, or False otherwise.
+ Also sets self.visible_children.
+ """
+
+ if self.opaque is not None:
+ return self.opaque
+
+ # A rotated image is never opaque. (This isn't actually true, but it
+ # saves us from the expensive calculations require to prove it is.)
+ if self.forward:
+ self.opaque = False
+ return False
+
+ rv = False
+ vc = [ ]
+
+ for i in self.children:
+ child, xo, yo, focus, main = i
+
+ if xo <= 0 and yo <= 0:
+ cw, ch = child.get_size()
+ if cw + xo < self.width or ch + yo < self.height:
+ if child.is_opaque():
+ vc = [ ]
+ rv = True
+
+ vc.append(i)
+
+ self.visible_children = vc
+ self.opaque = rv
+ return rv
+
+
+ def is_pixel_opaque(self, x, y):
+ """
+ Determine if the pixel at x and y is opaque or not.
+ """
+
+ if x < 0 or y < 0 or x >= self.width or y >= self.height:
+ return False
+
+ if self.is_opaque():
+ return True
+
+ return renpy.display.draw.is_pixel_opaque(self, x, y)
+
+
+ def fill(self, color):
+ """
+ Fills this Render with the given color.
+ """
+
+ color = renpy.easy.color(color)
+ solid = renpy.display.imagelike.Solid(color)
+ surf = render(solid, self.width, self.height, 0, 0)
+ self.blit(surf, (0, 0), focus=False, main=False)
+
+
+ def canvas(self):
+ """
+ Returns a canvas object that draws to this Render.
+ """
+
+ surf = renpy.display.pgrender.surface((self.width, self.height), True)
+
+ mutated_surface(surf)
+
+ self.blit(surf, (0, 0))
+
+ return Canvas(surf)
+
+
+class Canvas(object):
+
+ def __init__(self, surf): #@DuplicatedSignature
+ self.surf = surf
+
+ def rect(self, color, rect, width=0):
+
+ try:
+ blit_lock.acquire()
+ pygame.draw.rect(self.surf,
+ renpy.easy.color(color),
+ rect,
+ width)
+ finally:
+ blit_lock.release()
+
+ def polygon(self, color, pointlist, width=0):
+ try:
+ blit_lock.acquire()
+ pygame.draw.polygon(self.surf,
+ renpy.easy.color(color),
+ pointlist,
+ width)
+ finally:
+ blit_lock.release()
+
+ def circle(self, color, pos, radius, width=0):
+
+ try:
+ blit_lock.acquire()
+ pygame.draw.circle(self.surf,
+ renpy.easy.color(color),
+ pos,
+ radius,
+ width)
+
+ finally:
+ blit_lock.release()
+
+ def ellipse(self, color, rect, width=0):
+ try:
+ blit_lock.acquire()
+ pygame.draw.ellipse(self.surf,
+ renpy.easy.color(color),
+ rect,
+ width)
+ finally:
+ blit_lock.release()
+
+
+ def arc(self, color, rect, start_angle, stop_angle, width=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.arc(self.surf,
+ renpy.easy.color(color),
+ rect,
+ start_angle,
+ stop_angle,
+ width)
+ finally:
+ blit_lock.release()
+
+
+ def line(self, color, start_pos, end_pos, width=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.line(self.surf,
+ renpy.easy.color(color),
+ start_pos,
+ end_pos,
+ width)
+ finally:
+ blit_lock.release()
+
+ def lines(self, color, closed, pointlist, width=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.lines(self.surf,
+ renpy.easy.color(color),
+ closed,
+ pointlist,
+ width)
+ finally:
+ blit_lock.release()
+
+ def aaline(self, color, startpos, endpos, blend=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.aaline(self.surf,
+ renpy.easy.color(color),
+ startpos,
+ endpos,
+ blend)
+ finally:
+ blit_lock.release()
+
+ def aalines(self, color, closed, pointlist, blend=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.aalines(self.surf,
+ renpy.easy.color(color),
+ closed,
+ pointlist,
+ blend)
+ finally:
+ blit_lock.release()