summaryrefslogtreecommitdiff
path: root/unrpyc/renpy/display/behavior.py
diff options
context:
space:
mode:
Diffstat (limited to 'unrpyc/renpy/display/behavior.py')
-rw-r--r--unrpyc/renpy/display/behavior.py1531
1 files changed, 1531 insertions, 0 deletions
diff --git a/unrpyc/renpy/display/behavior.py b/unrpyc/renpy/display/behavior.py
new file mode 100644
index 0000000..02eccf2
--- /dev/null
+++ b/unrpyc/renpy/display/behavior.py
@@ -0,0 +1,1531 @@
+# 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.
+
+# This contains various Displayables that handle events.
+
+
+import renpy.display
+import renpy.audio
+
+from renpy.display.render import render, Render
+
+import pygame
+
+def compile_event(key, keydown):
+ """
+ Compiles a keymap entry into a python expression.
+
+ keydown determines if we are dealing with keys going down (press),
+ or keys going up (release).
+ """
+
+ # Lists or tuples get turned into or expressions.
+ if isinstance(key, (list, tuple)):
+ if not key:
+ return "(False)"
+
+ return "(" + " or ".join([compile_event(i, keydown) for i in key]) + ")"
+
+ # If it's in config.keymap, compile what's in config.keymap.
+ if key in renpy.config.keymap:
+ return compile_event(renpy.config.keymap[key], keydown)
+
+ if key is None:
+ return "(False)"
+
+ part = key.split("_")
+
+ # Deal with the mouse.
+ if part[0] == "mousedown":
+ if keydown:
+ return "(ev.type == %d and ev.button == %d)" % (pygame.MOUSEBUTTONDOWN, int(part[1]))
+ else:
+ return "(False)"
+
+ if part[0] == "mouseup":
+ if keydown:
+ return "(ev.type == %d and ev.button == %d)" % (pygame.MOUSEBUTTONUP, int(part[1]))
+ else:
+ return "(False)"
+
+ # Deal with the Joystick.
+ if part[0] == "joy":
+ if keydown:
+ return "(ev.type == %d and ev.press and ev.press == renpy.game.preferences.joymap.get(%r, None))" % (renpy.display.core.JOYEVENT, key)
+ else:
+ return "(ev.type == %d and ev.release and ev.release == renpy.game.preferences.joymap.get(%r, None))" % (renpy.display.core.JOYEVENT, key)
+
+ # Otherwise, deal with it as a key.
+ if keydown:
+ rv = "(ev.type == %d" % pygame.KEYDOWN
+ else:
+ rv = "(ev.type == %d" % pygame.KEYUP
+
+ if part[0] == "alt":
+ part.pop(0)
+ rv += " and (ev.mod & %d)" % pygame.KMOD_ALT
+ else:
+ rv += " and not (ev.mod & %d)" % pygame.KMOD_ALT
+
+ if part[0] == "meta":
+ part.pop(0)
+ rv += " and (ev.mod & %d)" % pygame.KMOD_META
+ else:
+ rv += " and not (ev.mod & %d)" % pygame.KMOD_META
+
+ if part[0] == "shift":
+ part.pop(0)
+ rv += " and (ev.mod & %d)" % pygame.KMOD_SHIFT
+
+ if part[0] == "noshift":
+ part.pop(0)
+ rv += " and not (ev.mod & %d)" % pygame.KMOD_SHIFT
+
+ if len(part) == 1:
+ if len(part[0]) != 1:
+ if renpy.config.developer:
+ raise Exception("Invalid key specifier %s" % key)
+ else:
+ return "(False)"
+
+ rv += " and ev.unicode == %r)" % part[0]
+
+ else:
+ if part[0] != "K":
+ if renpy.config.developer:
+ raise Exception("Invalid key specifier %s" % key)
+ else:
+ return "(False)"
+
+ key = "_".join(part)
+
+ rv += " and ev.key == %d)" % (getattr(pygame.constants, key))
+
+ return rv
+
+# These store a lambda for each compiled key in the system.
+event_cache = { }
+keyup_cache = { }
+
+def map_event(ev, name):
+ """Returns true if the event matches the named keycode being pressed."""
+
+ check_code = event_cache.get(name, None)
+ if check_code is None:
+ check_code = eval("lambda ev : " + compile_event(name, True), globals())
+ event_cache[name] = check_code
+
+ return check_code(ev)
+
+def map_keyup(ev, name):
+ """Returns true if the event matches the named keycode being released."""
+
+ check_code = keyup_cache.get(name, None)
+ if check_code is None:
+ check_code = eval("lambda ev : " + compile_event(name, False), globals())
+ keyup_cache[name] = check_code
+
+ return check_code(ev)
+
+
+def skipping(ev):
+ """
+ This handles setting skipping in response to the press of one of the
+ CONTROL keys. The library handles skipping in response to TAB.
+ """
+
+ if not renpy.config.allow_skipping:
+ return
+
+ if map_event(ev, "skip"):
+ renpy.config.skipping = "slow"
+ renpy.exports.restart_interaction()
+
+ if map_keyup(ev, "skip"):
+ renpy.config.skipping = None
+ renpy.exports.restart_interaction()
+
+ return
+
+
+def inspector(ev):
+ return map_event(ev, "inspector")
+
+
+##############################################################################
+# Utility functions for dealing with actions.
+
+def predict_action(var):
+ """
+ Predicts some of the actions that may be caused by a variable.
+ """
+
+ if var is None:
+ return
+
+ if isinstance(var, renpy.ui.Action):
+ var.predict()
+
+ if isinstance(var, (list, tuple)):
+ for i in var:
+ predict_action(i)
+
+def run(var, *args, **kwargs):
+ """
+ Runs a variable. This is done by calling all the functions, and
+ iterating over the lists and tuples.
+ """
+
+ if var is None:
+ return None
+
+ if isinstance(var, (list, tuple)):
+ rv = None
+
+ for i in var:
+ new_rv = run(i, *args, **kwargs)
+
+ if new_rv is not None:
+ rv = new_rv
+
+ return rv
+
+ return var(*args, **kwargs)
+
+def run_unhovered(var):
+ """
+ Calls the unhovered method on the variable, if it exists.
+ """
+
+ if var is None:
+ return None
+
+ if isinstance(var, (list, tuple)):
+ for i in var:
+ run_unhovered(i)
+
+ return
+
+ f = getattr(var, "unhovered", None)
+ if f is not None:
+ f()
+
+def run_periodic(var, st):
+
+ if isinstance(var, (list, tuple)):
+ rv = None
+
+ for i in var:
+ v = run_periodic(i, st)
+
+ if rv is None or v < rv:
+ rv = v
+
+ return rv
+
+ if isinstance(var, renpy.ui.Action):
+ return var.periodic(st)
+
+
+def is_selected(clicked):
+
+ if isinstance(clicked, (list, tuple)):
+ return any(is_selected(i) for i in clicked)
+
+ elif isinstance(clicked, renpy.ui.Action):
+ return clicked.get_selected()
+ else:
+ return False
+
+
+def is_sensitive(clicked):
+
+ if isinstance(clicked, (list, tuple)):
+ return all(is_sensitive(i) for i in clicked)
+
+ elif isinstance(clicked, renpy.ui.Action):
+ return clicked.get_sensitive()
+ else:
+ return True
+
+
+##############################################################################
+# Special-Purpose Displayables
+
+class Keymap(renpy.display.layout.Null):
+ """
+ This is a behavior that maps keys to actions that are called when
+ the key is pressed. The keys are specified by giving the appropriate
+ k_constant from pygame.constants, or the unicode for the key.
+ """
+
+ def __init__(self, replaces=None, **keymap):
+ super(Keymap, self).__init__(style='default')
+ self.keymap = keymap
+
+ def event(self, ev, x, y, st):
+
+ for name, action in self.keymap.items():
+ if map_event(ev, name):
+
+ rv = run(action)
+
+ if rv is not None:
+ return rv
+
+ raise renpy.display.core.IgnoreEvent()
+
+ def predict_one_action(self):
+ for i in self.keymap.values():
+ predict_action(i)
+
+
+class RollForward(renpy.display.layout.Null):
+ """
+ This behavior implements rollforward.
+ """
+
+ def __init__(self, value, **properties):
+ super(RollForward, self).__init__(**properties)
+ self.value = value
+
+
+ def event(self, ev, x, y, st):
+
+ if map_event(ev, "rollforward"):
+ renpy.game.interface.suppress_transition = True
+ renpy.game.after_rollback = True
+ renpy.game.log.rolled_forward = True
+ return self.value
+
+
+class PauseBehavior(renpy.display.layout.Null):
+ """
+ This is a class implementing the Pause behavior, which is to
+ return a value after a certain amount of time has elapsed.
+ """
+
+ def __init__(self, delay, result=False, **properties):
+ super(PauseBehavior, self).__init__(**properties)
+
+ self.delay = delay
+ self.result = result
+
+ def event(self, ev, x, y, st):
+
+ if st >= self.delay:
+
+ # If we have been drawn since the timeout, simply return
+ # true. Otherwise, force a redraw, and return true when
+ # it comes back.
+ if renpy.game.interface.drawn_since(st - self.delay):
+ return self.result
+ else:
+ renpy.game.interface.force_redraw = True
+
+
+ renpy.game.interface.timeout(max(self.delay - st, 0))
+
+class SoundStopBehavior(renpy.display.layout.Null):
+ """
+ This is a class implementing the sound stop behavior,
+ which is to return False when a sound is no longer playing
+ on the named channel.
+ """
+
+ def __init__(self, channel, result=False, **properties):
+ super(SoundStopBehavior, self).__init__(**properties)
+
+ self.channel = channel
+ self.result = result
+
+
+ def event(self, ev, x, y, st):
+
+ if not renpy.audio.music.get_playing(self.channel):
+ return self.result
+
+ renpy.game.interface.timeout(.025)
+
+
+class SayBehavior(renpy.display.layout.Null):
+ """
+ This is a class that implements the say behavior,
+ which is to return True (ending the interaction) if
+ the user presses space or enter, or clicks the left
+ mouse button.
+ """
+
+ focusable = True
+
+ def __init__(self, default=True, afm=None, dismiss=[ 'dismiss' ], allow_dismiss=None, **properties):
+ super(SayBehavior, self).__init__(default=default, **properties)
+
+ if not isinstance(dismiss, (list, tuple)):
+ dismiss = [ dismiss ]
+
+ if afm is not None:
+ self.afm_length = len(afm)
+ else:
+ self.afm_length = None
+
+ # What keybindings lead to dismissal?
+ self.dismiss = dismiss
+
+ self.allow_dismiss = allow_dismiss
+
+ def set_afm_length(self, afm_length):
+ self.afm_length = max(afm_length, 1)
+
+ def event(self, ev, x, y, st):
+
+ if self.afm_length and renpy.game.preferences.afm_time and renpy.game.preferences.afm_enable:
+
+ afm_delay = ( 1.0 * ( renpy.config.afm_bonus + self.afm_length ) / renpy.config.afm_characters ) * renpy.game.preferences.afm_time
+
+ if renpy.game.preferences.text_cps:
+ afm_delay += 1.0 / renpy.game.preferences.text_cps * self.afm_length
+
+ if st > afm_delay:
+ if renpy.config.afm_callback:
+ if renpy.config.afm_callback():
+ return True
+ else:
+ renpy.game.interface.timeout(0.1)
+ else:
+ return True
+ else:
+ renpy.game.interface.timeout(afm_delay - st)
+
+ for dismiss in self.dismiss:
+
+ if map_event(ev, dismiss) and self.is_focused():
+
+ if renpy.config.skipping:
+ renpy.config.skipping = None
+ renpy.exports.restart_interaction()
+ raise renpy.display.core.IgnoreEvent()
+
+ if renpy.game.preferences.using_afm_enable and renpy.game.preferences.afm_enable:
+ renpy.game.preferences.afm_enable = False
+ renpy.exports.restart_interaction()
+ raise renpy.display.core.IgnoreEvent()
+
+ if self.allow_dismiss:
+ if not self.allow_dismiss():
+ raise renpy.display.core.IgnoreEvent()
+
+ return True
+
+ skip_delay = renpy.config.skip_delay / 1000.0
+
+ if renpy.config.allow_skipping and renpy.config.skipping:
+
+ if st >= skip_delay:
+ if renpy.game.preferences.skip_unseen:
+ return True
+ elif renpy.config.skipping == "fast":
+ return True
+ elif renpy.game.context().seen_current(True):
+ return True
+ else:
+ renpy.game.interface.timeout(skip_delay - st)
+
+
+ return None
+
+
+##############################################################################
+# Button
+
+class Button(renpy.display.layout.Window):
+
+ keymap = { }
+ action = None
+
+ def __init__(self, child=None, style='button', clicked=None,
+ hovered=None, unhovered=None, action=None, role=None,
+ time_policy=None, keymap={},
+ **properties):
+
+ super(Button, self).__init__(child, style=style, **properties)
+
+ if isinstance(clicked, renpy.ui.Action):
+ action = clicked
+
+ if action is not None:
+ clicked = action
+
+ if not is_sensitive(action):
+ clicked = None
+
+ if role is None:
+ if action:
+ if is_selected(action):
+ role = 'selected_'
+ else:
+ role = ''
+ else:
+ role = ''
+
+ self.action = action
+ self.activated = False
+ self.clicked = clicked
+ self.hovered = hovered
+ self.unhovered = unhovered
+ self.focusable = clicked is not None
+ self.role = role
+ self.keymap = keymap
+
+ self.time_policy_data = None
+
+ def predict_one_action(self):
+ predict_action(self.clicked)
+ predict_action(self.hovered)
+ predict_action(self.unhovered)
+
+ if self.keymap:
+ for v in self.keymap.values():
+ predict_action(v)
+
+ def render(self, width, height, st, at):
+
+ if self.style.time_policy:
+ st, self.time_policy_data = self.style.time_policy(st, self.time_policy_data, self.style)
+
+ rv = super(Button, self).render(width, height, st, at)
+
+ if self.clicked:
+
+ rect = self.style.focus_rect
+ if rect is not None:
+ fx, fy, fw, fh = rect
+ else:
+ fx = self.style.left_margin
+ fy = self.style.top_margin
+ fw = rv.width - self.style.right_margin
+ fh = rv.height - self.style.bottom_margin
+
+ mask = self.style.focus_mask
+
+ if mask is True:
+ mask = rv
+ elif mask is not None:
+ mask = renpy.easy.displayable(mask)
+ mask = renpy.display.render.render(mask, rv.width, rv.height, st, at)
+
+ if mask is not None:
+ fmx = 0
+ fmy = 0
+ else:
+ fmx = None
+ fmy = None
+
+ rv.add_focus(self, None,
+ fx, fy, fw, fh,
+ fmx, fmy, mask)
+
+ return rv
+
+
+ def focus(self, default=False):
+ super(Button, self).focus(default)
+
+ if self.activated:
+ return None
+
+ rv = None
+
+ if not default:
+ rv = run(self.hovered)
+
+ self.set_transform_event(self.role + "hover")
+ self.child.set_transform_event(self.role + "hover")
+
+ return rv
+
+
+ def unfocus(self, default=False):
+ super(Button, self).unfocus(default)
+
+ if self.activated:
+ return None
+
+ if not default:
+ run_unhovered(self.hovered)
+ run(self.unhovered)
+
+ self.set_transform_event(self.role + "idle")
+ self.child.set_transform_event(self.role + "idle")
+
+
+ def per_interact(self):
+ if not self.clicked:
+ self.set_style_prefix(self.role + "insensitive_", True)
+ else:
+ self.set_style_prefix(self.role + "idle_", True)
+
+ super(Button, self).per_interact()
+
+ def event(self, ev, x, y, st):
+
+ # Call self.action.periodic()
+ timeout = run_periodic(self.action, st)
+
+ if timeout is not None:
+ renpy.game.interface.timeout(timeout)
+
+ # If we have a child, try passing the event to it. (For keyboard
+ # events, this only happens if we're focused.)
+ if self.is_focused() or not (ev.type == pygame.KEYDOWN or ev.type == pygame.KEYUP):
+ rv = super(Button, self).event(ev, x, y, st)
+ if rv is not None:
+ return rv
+
+ # If not focused, ignore all events.
+ if not self.is_focused():
+ return None
+
+ # Check the keymap.
+ for name, action in self.keymap.items():
+ if map_event(ev, name):
+ return run(action)
+
+ # Ignore as appropriate:
+ if map_event(ev, "button_ignore") and self.clicked:
+ raise renpy.display.core.IgnoreEvent()
+
+ # If clicked,
+ if map_event(ev, "button_select") and self.clicked:
+
+ self.activated = True
+ self.style.set_prefix(self.role + 'activate_')
+
+ if self.style.sound:
+ renpy.audio.music.play(self.style.sound, channel="sound")
+
+ rv = run(self.clicked)
+
+ if rv is not None:
+ return rv
+ else:
+ self.activated = False
+
+ if self.is_focused():
+ self.set_style_prefix(self.role + "hover_", True)
+ else:
+ self.set_style_prefix(self.role + "idle_", True)
+
+ raise renpy.display.core.IgnoreEvent()
+
+ return None
+
+
+ def set_style_prefix(self, prefix, root):
+ if root:
+ super(Button, self).set_style_prefix(prefix, root)
+
+
+# Reimplementation of the TextButton widget as a Button and a Text
+# widget.
+def TextButton(text, style='button', text_style='button_text',
+ clicked=None, **properties):
+
+ text = renpy.text.text.Text(text, style=text_style) #@UndefinedVariable
+ return Button(text, style=style, clicked=clicked, **properties)
+
+class ImageButton(Button):
+ """
+ Used to implement the guts of an image button.
+ """
+
+ def __init__(self,
+ idle_image,
+ hover_image,
+ insensitive_image = None,
+ activate_image = None,
+ selected_idle_image = None,
+ selected_hover_image = None,
+ selected_insensitive_image = None,
+ selected_activate_image = None,
+ style='image_button',
+ clicked=None,
+ hovered=None,
+ **properties):
+
+ insensitive_image = insensitive_image or idle_image
+ activate_image = activate_image or hover_image
+
+ selected_idle_image = selected_idle_image or idle_image
+ selected_hover_image = selected_hover_image or hover_image
+ selected_insensitive_image = selected_insensitive_image or insensitive_image
+ selected_activate_image = selected_activate_image or activate_image
+
+ self.state_children = dict(
+ idle_ = renpy.easy.displayable(idle_image),
+ hover_ = renpy.easy.displayable(hover_image),
+ insensitive_ = renpy.easy.displayable(insensitive_image),
+ activate_ = renpy.easy.displayable(activate_image),
+
+ selected_idle_ = renpy.easy.displayable(selected_idle_image),
+ selected_hover_ = renpy.easy.displayable(selected_hover_image),
+ selected_insensitive_ = renpy.easy.displayable(selected_insensitive_image),
+ selected_activate_ = renpy.easy.displayable(selected_activate_image),
+ )
+
+ super(ImageButton, self).__init__(renpy.display.layout.Null(),
+ style=style,
+ clicked=clicked,
+ hovered=hovered,
+ **properties)
+
+ def visit(self):
+ return list(self.state_children.values())
+
+ def get_child(self):
+ return self.style.child or self.state_children[self.style.prefix]
+
+
+# This is used for an input that takes its focus from a button.
+class HoveredProxy(object):
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+
+ def __call__(self):
+ self.a()
+ if self.b:
+ return self.b()
+
+
+class Input(renpy.text.text.Text): #@UndefinedVariable
+ """
+ This is a Displayable that takes text as input.
+ """
+
+ changed = None
+ prefix = ""
+ suffix = ""
+ caret_pos = 0
+
+ def __init__(self,
+ default="",
+ length=None,
+ style='input',
+ allow=None,
+ exclude=None,
+ prefix="",
+ suffix="",
+ changed=None,
+ button=None,
+ replaces=None,
+ editable=True,
+ **properties):
+
+ super(Input, self).__init__("", style=style, replaces=replaces, substitute=False, **properties)
+
+ self.content = str(default)
+ self.length = length
+
+ self.allow = allow
+ self.exclude = exclude
+ self.prefix = prefix
+ self.suffix = suffix
+
+ self.changed = changed
+
+ self.editable = editable
+
+ caretprops = { 'color' : None }
+
+ for i in properties:
+ if i.endswith("color"):
+ caretprops[i] = properties[i]
+
+ self.caret = renpy.display.image.Solid(xmaximum=1, style=style, **caretprops)
+ self.caret_pos = len(self.content)
+
+ if button:
+ self.editable = False
+ button.hovered = HoveredProxy(self.enable, button.hovered)
+ button.unhovered = HoveredProxy(self.disable, button.unhovered)
+
+ if isinstance(replaces, Input):
+ self.content = replaces.content
+ self.editable = replaces.editable
+ self.caret_pos = replaces.caret_pos
+
+ self.update_text(self.content, self.editable)
+
+
+ def update_text(self, content, editable):
+
+ if content != self.content or editable != self.editable:
+ renpy.display.render.redraw(self, 0)
+
+ if content != self.content:
+ self.content = content
+
+ if self.changed:
+ self.changed(content)
+
+ if content == "":
+ content = "\u200b"
+
+ self.editable = editable
+
+ # Choose the caret.
+ caret = self.style.caret
+ if caret is None:
+ caret = self.caret
+
+ if editable:
+ l = len(content)
+ self.set_text([self.prefix, content[0:self.caret_pos].replace("{", "{{"), caret,
+ content[self.caret_pos:l].replace("{", "{{"), self.suffix])
+ else:
+ self.set_text([self.prefix, content.replace("{", "{{"), self.suffix ])
+
+ # This is needed to ensure the caret updates properly.
+ def set_style_prefix(self, prefix, root):
+ if prefix != self.style.prefix:
+ self.update_text(self.content, self.editable)
+
+ super(Input, self).set_style_prefix(prefix, root)
+
+ def enable(self):
+ self.update_text(self.content, True)
+
+ def disable(self):
+ self.update_text(self.content, False)
+
+ def event(self, ev, x, y, st):
+
+ if not self.editable:
+ return None
+
+ l = len(self.content)
+
+ if map_event(ev, "input_backspace"):
+
+ if self.content and self.caret_pos > 0:
+ content = self.content[0:self.caret_pos-1] + self.content[self.caret_pos:l]
+ self.caret_pos -= 1
+ self.update_text(content, self.editable)
+
+ renpy.display.render.redraw(self, 0)
+ raise renpy.display.core.IgnoreEvent()
+
+ elif map_event(ev, "input_enter"):
+ if not self.changed:
+ return self.content
+
+ elif map_event(ev, "input_left"):
+ if self.caret_pos > 0:
+ self.caret_pos -= 1
+ self.update_text(self.content, self.editable)
+
+ renpy.display.render.redraw(self, 0)
+ raise renpy.display.core.IgnoreEvent()
+
+ elif map_event(ev, "input_right"):
+ if self.caret_pos < l:
+ self.caret_pos += 1
+ self.update_text(self.content, self.editable)
+
+ renpy.display.render.redraw(self, 0)
+ raise renpy.display.core.IgnoreEvent()
+
+ elif map_event(ev, "input_delete"):
+ if self.caret_pos < l:
+ content = self.content[0:self.caret_pos] + self.content[self.caret_pos+1:l]
+ self.update_text(content, self.editable)
+
+ renpy.display.render.redraw(self, 0)
+ raise renpy.display.core.IgnoreEvent()
+
+ elif ev.type == pygame.KEYDOWN and ev.str:
+ if ord(ev.str[0]) < 32:
+ return None
+
+ if self.length and len(self.content) >= self.length:
+ raise renpy.display.core.IgnoreEvent()
+
+ if self.allow and ev.str not in self.allow:
+ raise renpy.display.core.IgnoreEvent()
+
+ if self.exclude and ev.str in self.exclude:
+ raise renpy.display.core.IgnoreEvent()
+
+ content = self.content[0:self.caret_pos] + ev.str + self.content[self.caret_pos:l]
+ self.caret_pos += 1
+
+ self.update_text(content, self.editable)
+
+ raise renpy.display.core.IgnoreEvent()
+
+# A map from adjustment to lists of displayables that want to be redrawn
+# if the adjustment changes.
+adj_registered = { }
+
+# This class contains information about an adjustment that can change the
+# position of content.
+class Adjustment(renpy.object.Object):
+ """
+ :doc: ui
+ :name: ui.adjustment class
+
+ Adjustment objects represent a value that can be adjusted by a bar
+ or viewport. They contain information about the value, the range
+ of the value, and how to adjust the value in small steps and large
+ pages.
+
+
+ """
+
+ def __init__(self, range=1, value=0, step=None, page=0, changed=None, adjustable=None, ranged=None): #@ReservedAssignment
+ """
+ The following parameters correspond to fields or properties on
+ the adjustment object:
+
+ `range`
+ The range of the adjustment, a number.
+
+ `value`
+ The value of the adjustment, a number.
+
+ `step`
+ The step size of the adjustment, a number. If None, then
+ defaults to 1/10th of a page, if set. Otherwise, defaults
+ to the 1/20th of the range.
+
+ This is used when scrolling a viewport with the mouse wheel.
+
+ `page`
+ The page size of the adjustment. If None, this is set
+ automatically by a viewport. If never set, defaults to 1/10th
+ of the range.
+
+ It's can be used when clicking on a scrollbar.
+
+ The following parameters control the behavior of the adjustment.
+
+ `adjustable`
+ If True, this adjustment can be changed by a bar. If False,
+ it can't.
+
+ It defaults to being adjustable if a `changed` function
+ is given or if the adjustment is associated with a viewport,
+ and not adjustable otherwise.
+
+ `changed`
+ This function is called with the new value when the value of
+ the adjustment changes.
+
+ `ranged`
+ This function is called with the adjustment object when
+ the range of the adjustment is set by a viewport.
+
+ .. method:: change(value)
+
+ Changes the value of the adjustment to `value`, updating
+ any bars and viewports that use the adjustment.
+ """
+
+
+ super(Adjustment, self).__init__()
+
+ if adjustable is None:
+ if changed:
+ adjustable = True
+
+ self._value = value
+ self._range = range
+ self._page = page
+ self._step = step
+ self.changed = changed
+ self.adjustable = adjustable
+ self.ranged = ranged
+
+ def get_value(self):
+ if self._value > self._range:
+ return self._range
+
+ return self._value
+
+ def set_value(self, v):
+ self._value = v
+
+ value = property(get_value, set_value)
+
+ def get_range(self):
+ return self._range
+
+ def set_range(self, v):
+ self._range = v
+ if self.ranged:
+ self.ranged(self)
+
+ range = property(get_range, set_range) #@ReservedAssignment
+
+ def get_page(self):
+ if self._page is not None:
+ return self._page
+
+ return self._range / 10
+
+ def set_page(self, v):
+ self._page = v
+
+ page = property(get_page, set_page)
+
+ def get_step(self):
+ if self._step is not None:
+ return self._step
+
+ if self._page is not None and self.page > 0:
+ return self._page / 10
+
+ if isinstance(self._range, float):
+ return self._range / 10
+ else:
+ return 1
+
+ def set_step(self, v):
+ self._step = v
+
+ step = property(get_step, set_step)
+
+ # Register a displayable to be redrawn when this adjustment changes.
+ def register(self, d):
+ adj_registered.setdefault(self, [ ]).append(d)
+
+ def change(self, value):
+
+ if value < 0:
+ value = 0
+ if value > self._range:
+ value = self._range
+
+ if value != self._value:
+ self._value = value
+ for d in adj_registered.setdefault(self, [ ]):
+ renpy.display.render.redraw(d, 0)
+ if self.changed:
+ return self.changed(value)
+
+ return None
+
+class Bar(renpy.display.core.Displayable):
+ """
+ Implements a bar that can display an integer value, and respond
+ to clicks on that value.
+ """
+
+ __version__ = 2
+
+ def after_upgrade(self, version):
+
+ if version < 1:
+ self.adjustment = Adjustment(self.range, self.value, changed=self.changed) # E1101
+ self.adjustment.register(self)
+ del self.range # E1101
+ del self.value # E1101
+ del self.changed # E1101
+
+ if version < 2:
+ self.value = None
+
+ def __init__(self,
+ range=None, #@ReservedAssignment
+ value=None,
+ width=None,
+ height=None,
+ changed=None,
+ adjustment=None,
+ step=None,
+ page=None,
+ bar=None,
+ style=None,
+ vertical=False,
+ replaces=None,
+ hovered=None,
+ unhovered=None,
+ **properties):
+
+ self.value = None
+
+ if adjustment is None:
+ if isinstance(value, renpy.ui.BarValue):
+
+ if isinstance(replaces, Bar):
+ value.replaces(replaces.value)
+
+ self.value = value
+ adjustment = value.get_adjustment()
+ renpy.game.interface.timeout(0)
+ else:
+ adjustment = Adjustment(range, value, step=step, page=page, changed=changed)
+
+ if style is None:
+ if self.value is not None:
+ if vertical:
+ style = self.value.get_style()[1]
+ else:
+ style = self.value.get_style()[0]
+ else:
+ if vertical:
+ style = 'vbar'
+ else:
+ style = 'bar'
+
+ if width is not None:
+ properties['xmaximum'] = width
+
+ if height is not None:
+ properties['ymaximum'] = height
+
+ super(Bar, self).__init__(style=style, **properties)
+
+ self.adjustment = adjustment
+ self.focusable = True
+
+ # These are set when we are first rendered.
+ self.thumb_dim = 0
+ self.height = 0
+ self.width = 0
+ self.hidden = False
+
+ self.hovered = hovered
+ self.unhovered = unhovered
+
+ def per_interact(self):
+ self.focusable = self.adjustment.adjustable
+ self.adjustment.register(self)
+
+ def predict_one(self):
+ pd = renpy.display.predict.displayable
+ style = self.style
+
+ pd(style.insensitive_fore_bar)
+ pd(style.idle_fore_bar)
+ pd(style.hover_fore_bar)
+ pd(style.selected_idle_fore_bar)
+ pd(style.selected_hover_fore_bar)
+
+ pd(style.insensitive_aft_bar)
+ pd(style.idle_aft_bar)
+ pd(style.hover_aft_bar)
+ pd(style.selected_idle_aft_bar)
+ pd(style.selected_hover_aft_bar)
+
+ pd(style.insensitive_thumb)
+ pd(style.idle_thumb)
+ pd(style.hover_thumb)
+ pd(style.selected_idle_thumb)
+ pd(style.selected_hover_thumb)
+
+ pd(style.insensitive_thumb_shadow)
+ pd(style.idle_thumb_shadow)
+ pd(style.hover_thumb_shadow)
+ pd(style.selected_idle_thumb_shadow)
+ pd(style.selected_hover_thumb_shadow)
+
+ def render(self, width, height, st, at):
+
+ # Handle redrawing.
+ if self.value is not None:
+ redraw = self.value.periodic(st)
+
+ if redraw is not None:
+ renpy.display.render.redraw(self, redraw)
+
+ # Store the width and height for the event function to use.
+ self.width = width
+ self.height = height
+ range = self.adjustment.range #@ReservedAssignment
+ value = self.adjustment.value
+ page = self.adjustment.page
+
+ if range <= 0:
+ if self.style.unscrollable == "hide":
+ self.hidden = True
+ return renpy.display.render.Render(width, height)
+ elif self.style.unscrollable == "insensitive":
+ self.set_style_prefix("insensitive_", True)
+
+ self.hidden = False
+
+ if self.style.bar_invert ^ self.style.bar_vertical:
+ value = range - value
+
+ bar_vertical = self.style.bar_vertical
+
+ if bar_vertical:
+ dimension = height
+ else:
+ dimension = width
+
+ fore_gutter = self.style.fore_gutter
+ aft_gutter = self.style.aft_gutter
+
+ active = dimension - fore_gutter - aft_gutter
+ if range:
+ thumb_dim = active * page / (range + page)
+ else:
+ thumb_dim = active
+
+ thumb_offset = abs(self.style.thumb_offset)
+
+ if bar_vertical:
+ thumb = render(self.style.thumb, width, thumb_dim, st, at)
+ thumb_shadow = render(self.style.thumb_shadow, width, thumb_dim, st, at)
+ thumb_dim = thumb.height
+ else:
+ thumb = render(self.style.thumb, thumb_dim, height, st, at)
+ thumb_shadow = render(self.style.thumb_shadow, thumb_dim, height, st, at)
+ thumb_dim = thumb.width
+
+ # Remove the offset from the thumb.
+ thumb_dim -= thumb_offset * 2
+ self.thumb_dim = thumb_dim
+
+ active -= thumb_dim
+
+ if range:
+ fore_size = active * value / range
+ else:
+ fore_size = active
+
+ fore_size = int(fore_size)
+
+ aft_size = active - fore_size
+
+ fore_size += fore_gutter
+ aft_size += aft_gutter
+
+ rv = renpy.display.render.Render(width, height)
+
+ if bar_vertical:
+
+ if self.style.bar_resizing:
+ foresurf = render(self.style.fore_bar, width, fore_size, st, at)
+ aftsurf = render(self.style.aft_bar, width, aft_size, st, at)
+ rv.blit(thumb_shadow, (0, fore_size - thumb_offset))
+ rv.blit(foresurf, (0, 0), main=False)
+ rv.blit(aftsurf, (0, height-aft_size), main=False)
+ rv.blit(thumb, (0, fore_size - thumb_offset))
+
+ else:
+ foresurf = render(self.style.fore_bar, width, height, st, at)
+ aftsurf = render(self.style.aft_bar, width, height, st, at)
+
+ rv.blit(thumb_shadow, (0, fore_size - thumb_offset))
+ rv.blit(foresurf.subsurface((0, 0, width, fore_size)), (0, 0), main=False)
+ rv.blit(aftsurf.subsurface((0, height - aft_size, width, aft_size)), (0, height - aft_size), main=False)
+ rv.blit(thumb, (0, fore_size - thumb_offset))
+
+ else:
+ if self.style.bar_resizing:
+ foresurf = render(self.style.fore_bar, fore_size, height, st, at)
+ aftsurf = render(self.style.aft_bar, aft_size, height, st, at)
+ rv.blit(thumb_shadow, (fore_size - thumb_offset, 0))
+ rv.blit(foresurf, (0, 0), main=False)
+ rv.blit(aftsurf, (width-aft_size, 0), main=False)
+ rv.blit(thumb, (fore_size - thumb_offset, 0))
+
+ else:
+ foresurf = render(self.style.fore_bar, width, height, st, at)
+ aftsurf = render(self.style.aft_bar, width, height, st, at)
+
+ rv.blit(thumb_shadow, (fore_size - thumb_offset, 0))
+ rv.blit(foresurf.subsurface((0, 0, fore_size, height)), (0, 0), main=False)
+ rv.blit(aftsurf.subsurface((width - aft_size, 0, aft_size, height)), (width-aft_size, 0), main=False)
+ rv.blit(thumb, (fore_size - thumb_offset, 0))
+
+ if self.focusable:
+ rv.add_focus(self, None, 0, 0, width, height)
+
+ return rv
+
+
+ def focus(self, default=False):
+ super(Bar, self).focus(default)
+ self.set_transform_event("hover")
+
+ if not default:
+ run(self.hovered)
+
+
+ def unfocus(self, default=False):
+ super(Bar, self).unfocus()
+ self.set_transform_event("idle")
+
+ if not default:
+ run_unhovered(self.hovered)
+ run(self.unhovered)
+
+ def event(self, ev, x, y, st):
+
+ if not self.focusable:
+ return None
+
+ if not self.is_focused():
+ return None
+
+ if self.hidden:
+ return None
+
+ range = self.adjustment.range #@ReservedAssignment
+ old_value = self.adjustment.value
+ value = old_value
+
+ vertical = self.style.bar_vertical
+ invert = self.style.bar_invert ^ vertical
+ if invert:
+ value = range - value
+
+ grabbed = (renpy.display.focus.get_grab() is self)
+ just_grabbed = False
+
+ if not grabbed and map_event(ev, "bar_activate"):
+ renpy.display.focus.set_grab(self)
+ just_grabbed = True
+ grabbed = True
+
+ if grabbed:
+
+ if vertical:
+ increase = "bar_down"
+ decrease = "bar_up"
+ else:
+ increase = "bar_right"
+ decrease = "bar_left"
+
+ if map_event(ev, decrease):
+ value -= self.adjustment.step
+
+ if map_event(ev, increase):
+ value += self.adjustment.step
+
+ if ev.type in (pygame.MOUSEMOTION, pygame.MOUSEBUTTONUP, pygame.MOUSEBUTTONDOWN):
+
+ if vertical:
+
+ tgutter = self.style.fore_gutter
+ bgutter = self.style.aft_gutter
+ zone_height = self.height - tgutter - bgutter - self.thumb_dim
+ if zone_height:
+ value = (y - tgutter - self.thumb_dim / 2) * range / zone_height
+ else:
+ value = 0
+
+ else:
+ lgutter = self.style.fore_gutter
+ rgutter = self.style.aft_gutter
+ zone_width = self.width - lgutter - rgutter - self.thumb_dim
+ if zone_width:
+ value = (x - lgutter - self.thumb_dim / 2) * range / zone_width
+ else:
+ value = 0
+
+ if isinstance(range, int):
+ value = int(value)
+
+ if value < 0:
+ value = 0
+
+ if value > range:
+ value = range
+
+ if invert:
+ value = range - value
+
+ if grabbed and not just_grabbed and map_event(ev, "bar_deactivate"):
+ renpy.display.focus.set_grab(None)
+
+ if value != old_value:
+ return self.adjustment.change(value)
+
+ return None
+
+
+class Conditional(renpy.display.layout.Container):
+ """
+ This class renders its child if and only if the condition is
+ true. Otherwise, it renders nothing. (Well, a Null).
+
+ Warning: the condition MUST NOT update the game state in any
+ way, as that would break rollback.
+ """
+
+ def __init__(self, condition, *args, **properties):
+ super(Conditional, self).__init__(*args, **properties)
+
+ self.condition = condition
+ self.null = renpy.display.layout.Null()
+
+ self.state = eval(self.condition, vars(renpy.store))
+
+ def render(self, width, height, st, at):
+ if self.state:
+ return render(self.child, width, height, st, at)
+ else:
+ return render(self.null, width, height, st, at)
+
+ def event(self, ev, x, y, st):
+
+ state = eval(self.condition, vars(renpy.store))
+
+ if state != self.state:
+ renpy.display.render.redraw(self, 0)
+
+ self.state = state
+
+ if state:
+ return self.child.event(ev, x, y, st)
+
+
+class TimerState(renpy.python.RevertableObject):
+ """
+ Stores the state of the timer, which may need to be rolled back.
+ """
+
+ # Prevents us from having to worry about our initialization being
+ # rolled back.
+ started = False
+ next_event = None
+
+class Timer(renpy.display.layout.Null):
+
+ __version__ = 1
+
+ started = False
+
+ def after_upgrade(self, version):
+ if version < 1:
+ self.state = TimerState()
+ self.state.started = self.started
+ self.state.next_event = self.next_event
+
+ def __init__(self, delay, action=None, repeat=False, args=(), kwargs={}, replaces=None, **properties):
+ super(Timer, self).__init__(**properties)
+
+ if action is None:
+ raise Exception("A timer must have an action supplied.")
+
+ if delay <= 0:
+ raise Exception("A timer's delay must be > 0.")
+
+ # The delay.
+ self.delay = delay
+
+ # Should we repeat the event?
+ self.repeat = repeat
+
+ # The time the next event should occur.
+ self.next_event = None
+
+ # The function and its arguments.
+ self.function = action
+ self.args = args
+ self.kwargs = kwargs
+
+ # Did we start the timer?
+ self.started = False
+
+ if replaces is not None:
+ self.state = replaces.state
+ else:
+ self.state = TimerState()
+
+
+ def event(self, ev, x, y, st):
+
+ state = self.state
+
+ if not state.started:
+ state.started = True
+ state.next_event = st + self.delay
+
+ if state.next_event is None:
+ return
+
+ if st < state.next_event:
+ renpy.game.interface.timeout(state.next_event - st)
+ return
+
+ if not self.repeat:
+ state.next_event = None
+ else:
+ state.next_event = state.next_event + self.delay
+ if state.next_event < st:
+ state.next_event = st + self.delay
+
+ renpy.game.interface.timeout(state.next_event - st)
+
+ return run(self.function, *self.args, **self.kwargs)
+
+
+class MouseArea(renpy.display.core.Displayable):
+
+ def __init__(self, hovered=None, unhovered=None, replaces=None, **properties):
+ super(MouseArea, self).__init__(**properties)
+
+ self.hovered = hovered
+ self.unhovered = unhovered
+
+ # Are we hovered right now?
+ self.is_hovered = False
+
+ if replaces is not None:
+ self.is_hovered = replaces.is_hovered
+
+ # Taken from the render.
+ self.width = 0
+ self.height = 0
+
+
+ def render(self, width, height, st, at):
+ self.width = width
+ self.height = height
+
+ return Render(width, height)
+
+ def event(self, ev, x, y, st):
+
+ if 0 <= x < self.width and 0 <= y < self.height:
+ is_hovered = True
+ else:
+ is_hovered = False
+
+ if is_hovered and not self.is_hovered:
+ self.is_hovered = True
+
+ return run(self.hovered)
+
+ elif not is_hovered and self.is_hovered:
+ self.is_hovered = False
+
+ run_unhovered(self.hovered)
+ run(self.unhovered)
+
+