class ButtonPress(CallbackState): def __init__(self, buttons=None, correct_resp=None, base_time=None, duration=None, parent=None, save_log=True, name=None): super(ButtonPress, self).__init__(parent=parent, duration=duration, save_log=save_log, name=name) if buttons is None: self.__buttons = [] elif type(buttons) not in (list, tuple): self.__buttons = [buttons] else: self.__buttons = buttons self._button_names = None self._init_correct_resp = correct_resp self._init_base_time = None self._pressed = '' self._press_time = {"time": None, "error": None} self._correct = False self._rt = None self.__pressed_ref = None # append log vars self._log_attrs.extend(['button_names', 'correct_resp', 'base_time', 'pressed', 'press_time', 'correct', 'rt']) self.__parallel = None def _enter(self): self._button_names = [button._name for button in self.__buttons] if self._correct_resp is None: self._correct_resp = [] elif type(self.correct_resp) not in (list, tuple): self._correct_resp = [self._correct_resp] self.__pressed_ref = Ref( lambda lst: [name for name, down in lst if down], [(button.name, button.state == "down") for button in self.__buttons]) super(ButtonPress, self)._enter() def _callback(self): if self._base_time is None: self._base_time = self._start_time self._pressed = '' self._press_time = None self._correct = False self._rt = None self.__pressed_ref.add_change_callback(self.button_callback) def button_callback(self): self.claim_exceptions() pressed_list = self.__pressed_ref.eval() if not len(pressed_list): return button = pressed_list[0] self._pressed = button self._press_time = self._exp._app.event_time # calc RT if something pressed self._rt = self._press_time['time'] - self._base_time if self._pressed in self._correct_resp: self._correct = True # let's leave b/c we're all done self.cancel(self._press_time['time']) def _leave(self): self.__pressed_ref.remove_change_callback(self.button_callback) super(ButtonPress, self)._leave() def __enter__(self): if self.__parallel is not None: raise RuntimeError("ButtonPress context is not reentrant!") #!!! #TODO: make sure we're the previous state? self.__parallel = Parallel(name="BUTTONPRESS") self.__parallel.override_instantiation_context() self.__parallel.claim_child(self) self.__parallel.__enter__() return self def __exit__(self, type, value, tb): ret = self.__parallel.__exit__(type, value, tb) for n in range(1, len(self.__parallel._children)): self.__parallel.set_child_blocking(n, False) self.__buttons.extend(iter_nested_buttons(self.__parallel)) self.__parallel = None return ret
class WidgetState(VisualState): layout_stack = [] property_aliases = { "left": "x", "bottom": "y", "left_bottom": "pos", "left_center": ("x", "center_y"), "left_top": ("x", "top"), "center_bottom": ("center_x", "y"), "center_top": ("center_x", "top"), "right_bottom": ("right", "y"), "right_center": ("right", "center_y"), "right_top": ("right", "top") } @classmethod def wrap(cls, widget_class, name=None): if not issubclass(widget_class, kivy.uix.widget.Widget): raise ValueError( "widget_class must be a subclass of kivy.uix.widget.Widget") if name is None: name = widget_class.__name__ def __init__(self, *pargs, **kwargs): cls.__init__(self, widget_class, *pargs, **kwargs) return type(name, (cls,), {"__init__" : __init__}) def __init__(self, widget_class, duration=None, parent=None, save_log=True, name=None, index=0, layout=None, **params): super(WidgetState, self).__init__(parent=parent, duration=duration, save_log=save_log, name=name) self.__issued_refs = weakref.WeakValueDictionary() self.__widget_param_names = widget_class().properties().keys() self.__widget_class = widget_class self._init_index = index self._widget = None self.__parent_widget = None self._constructor_param_names = params.keys() self._init_constructor_params = params for name, value in params.iteritems(): setattr(self, "_init_" + name, value) if layout is None: if len(WidgetState.layout_stack): self.__layout = WidgetState.layout_stack[-1] else: self.__layout = None else: self.__layout = layout self.__x_pos_mode = None self.__y_pos_mode = None # set the log attrs self._log_attrs.extend(['constructor_params']) self.__parallel = None def get_current_param(self, name): return getattr(self.current_clone._widget, name) def __getattr__(self, name): try: return self.__issued_refs[name] except KeyError: try: props = WidgetState.property_aliases[name] except KeyError: if name in self.__widget_param_names: props = name else: return super(WidgetState, self).__getattr__(name) if isinstance(props, str): ref = Ref(self.get_current_param, props) elif isinstance(props, tuple): ref = tuple(Ref(self.get_current_param, prop) for prop in props) else: raise RuntimeError("Bad value for 'props': %r" % props) self.__issued_refs[name] = ref return ref def property_callback(self, name, *pargs): try: ref = self.__issued_refs[name] except KeyError: return ref.dep_changed() def eval_init_refs(self): return self.transform_params(self.apply_aliases( {name : getattr(self, "_" + name) for name in self._constructor_param_names})) def apply_aliases(self, params): new_params = {} for name, value in params.items(): props = WidgetState.property_aliases.get(name, name) if isinstance(props, str): new_params[props] = value elif isinstance(props, tuple): for n, prop in enumerate(props): new_params[prop] = subvalue[n] else: raise RuntimeError("Bad value for 'props': %r" % props) return new_params def transform_params(self, params): for name, value in params.iteritems(): params[name] = self.transform_param(name, value) return params def transform_param(self, name, value): value = val(value) # normalize color specifier... if value is not None and "color" in name: return normalize_color_spec(value) else: return value def resolve_params(self, params): # remove kivy's default size hints... if "size_hint" not in params: params.setdefault("size_hint_x", None) params.setdefault("size_hint_y", None) return params def construct(self, params): self._widget = self.__widget_class(**params) self.live_change(**params) self._widget.bind(**{name : partial(self.property_callback, name) for name in self.__widget_param_names}) def show(self): if self.__layout is None: self.__parent_widget = self._exp._app.wid else: self.__parent_widget = self.__layout._widget self.__parent_widget.add_widget(self._widget, index=self._index) def unshow(self): self.__parent_widget.remove_widget(self._widget) self.__parent_widget = None def live_change(self, **params): xy_pos_props = {"pos": "min", "center": "mid"} x_pos_props = {"x": "min", "center_x": "mid", "right": "max"} y_pos_props = {"y": "min", "center_y": "mid", "top": "max"} pos_props = (xy_pos_props.keys() + x_pos_props.keys() + y_pos_props.keys()) new_x_pos_mode = None new_y_pos_mode = None for prop, mode in xy_pos_props.iteritems(): if prop in params: new_x_pos_mode = mode new_y_pos_mode = mode break else: for prop, mode in x_pos_props.iteritems(): if prop in params: new_x_pos_mode = mode break for prop, mode in y_pos_props.iteritems(): if prop in params: new_y_pos_mode = mode break if new_x_pos_mode is not None: self.__x_pos_mode = new_x_pos_mode elif self.__x_pos_mode is None: params["center_x"] = self._exp.screen.center_x.eval() elif self.__x_pos_mode == "min": params["x"] = self._widget.x elif self.__x_pos_mode == "mid": params["center_x"] = self._widget.center_x elif self.__x_pos_mode == "max": params["right"] = self._widget.right if new_y_pos_mode is not None: self.__y_pos_mode = new_y_pos_mode elif self.__y_pos_mode is None: params["center_y"] = self._exp.screen.center_y.eval() elif self.__y_pos_mode == "min": params["y"] = self._widget.y elif self.__y_pos_mode == "mid": params["center_y"] = self._widget.center_y elif self.__y_pos_mode == "max": params["top"] = self._widget.top for name, value in params.iteritems(): if name not in pos_props: setattr(self._widget, name, value) for name, value in params.iteritems(): if name in pos_props: setattr(self._widget, name, value) def animate(self, duration=None, parent=None, save_log=True, name=None, **anim_params): anim = Animate(self, duration=duration, parent=parent, name=name, save_log=save_log, **anim_params) anim.override_instantiation_context() return anim def slide(self, duration=None, speed=None, accel=None, parent=None, save_log=True, name=None, **params): def interp(a, b, w): if isinstance(a, dict): return {name : interp(a[name], b[name], w) for name in set(a) & set(b)} elif hasattr(a, "__iter__"): return [interp(a_prime, b_prime, w) for a_prime, b_prime in zip(a, b)] else: return a * (1.0 - w) + b * w condition = duration is None, speed is None, accel is None if condition == (False, True, True): # simple, linear interpolation anim_params = {} for param_name, value in params.items(): def func(t, initial, value=value, param_name=param_name): new_value = self.transform_param(param_name, value) return interp(initial, new_value, t / duration) anim_params[param_name] = func #TODO: fancier interpolation modes!!! else: raise ValueError("Invalid combination of parameters.") #... anim = self.animate(duration=duration, parent=parent, save_log=save_log, name=name, **anim_params) anim.override_instantiation_context() return anim def set_appear_time(self, appear_time): self._appear_time = appear_time clock.schedule(self.leave) def set_disappear_time(self, disappear_time): self._disappear_time = disappear_time clock.schedule(self.finalize) def _enter(self): super(WidgetState, self)._enter() self.__x_pos_mode = None self.__y_pos_mode = None params = self.eval_init_refs() params = self.resolve_params(params) self.construct(params) def __enter__(self): if self.__parallel is not None: raise RuntimeError("WidgetState context is not reentrant!") #!!! #TODO: make sure we're the previous state? WidgetState.layout_stack.append(self) self.__parallel = Parallel(name="LAYOUT") self.__parallel.override_instantiation_context() self.__parallel.claim_child(self) self.__parallel.__enter__() return self def __exit__(self, type, value, tb): ret = self.__parallel.__exit__(type, value, tb) if self._init_duration is None: self.__parallel.set_child_blocking(0, False) else: for n in range(1, len(self.__parallel._children)): self.__parallel.set_child_blocking(n, False) self.__parallel = None if len(WidgetState.layout_stack): WidgetState.layout_stack.pop() return ret