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 __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 DynamicDotBox(self, duration=None, update_interval=jitter(1 / 20., (1 / 10.) - (1 / 20.)), **dotbox_args): """Display random dots that update at an interval. Parameters ---------- duration : float or None Duration to show the random dots. update_interval : float How often to update the random dots. Default is to jitter between 10 and 20 Hz. dotbox_args : kwargs See the DotBox for any kwargs options to control the DotBox Note: You can access the dotbox via the `db` attribute of the subroutine. Examples -------- Display a dynamic dot box with 40 dots for 3 seconds: :: DynamicDotBox(size=(500, 500), num_dots=40, duration=3.0) Display two dynamic dot boxes side-by-side until a key press: :: with Parallel(): ddb1 = DynamicDotBox(center_x=exp.screen.center_x-200, num_dots=40, size=(400, 400)) ddb2 = DynamicDotBox(center_x=exp.screen.center_x+200, num_dots=80, size=(400, 400)) with UntilDone(): kp = KeyPress() Log(appear_time=ddb1.db.appear_time) """ # show the dotbox with Parallel(): db = DotBox(duration=duration, **dotbox_args) self.db = db with Meanwhile(): # redraw the dots self.start_dots = db.num_dots with Loop() as l: Wait(duration=update_interval) # hack to make 1.8 work with If((l.i % 2) == 0): self.ndots = self.start_dots + .01 with Else(): self.ndots = self.start_dots #db.update(save_log=False, **dotbox_args) db.update(save_log=False, num_dots=self.ndots)
KeyPress() g = Grating(width=500, height=500, envelope='Gaussian', frequency=75, phase=11.0, color_one='blue', color_two='red', contrast=0.25) with UntilDone(): KeyPress() g.update(bottom=exp.screen.center) KeyPress() with Parallel(): g = Grating(width=256, height=256, frequency=20, envelope='Circular', std_dev=7.5, contrast=0.75, color_one='green', color_two='orange') lbl = Label(text='Grating!', bottom=g.top) with UntilDone(): # kp = KeyPress() with Parallel(): g.slide(phase=-8 * math.pi, frequency=10., bottom=exp.screen.bottom,
class ButtonPress(CallbackState): def __init__(self, buttons=None, correct_resp=None, base_time=None, duration=None, parent=None, save_log=True, name=None, blocking=True): super(ButtonPress, self).__init__(parent=parent, duration=duration, save_log=save_log, name=name, blocking=blocking) 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 child in self.__parallel._children[1:]: child._blocking = 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, blocking=True, index=0, layout=None, **params): super(WidgetState, self).__init__(parent=parent, duration=duration, save_log=save_log, name=name, blocking=blocking) 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 attribute_update_state(self, name, value): if name in self.__widget_param_names: return UpdateWidget(self, **{name : value}) else: raise AttributeError("%r is not a property of this widget (%r)." % (name, self)) 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] = value[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._children[0]._blocking = False else: for child in self.__parallel._children[1:]: child._blocking = False self.__parallel = None if len(WidgetState.layout_stack): WidgetState.layout_stack.pop() return ret
class ButtonPress(CallbackState): def __init__(self, buttons=None, correct_resp=None, base_time=None, duration=None, parent=None, save_log=True, name=None, blocking=True): super(ButtonPress, self).__init__(parent=parent, duration=duration, save_log=save_log, name=name, blocking=blocking) 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 child in self.__parallel._children[1:]: child._blocking = 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, blocking=True, index=0, layout=None, **params): super(WidgetState, self).__init__(parent=parent, duration=duration, save_log=save_log, name=name, blocking=blocking) 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 attribute_update_state(self, name, value): if name in self.__widget_param_names: return UpdateWidget(self, **{name: value}) else: raise AttributeError("%r is not a property of this widget (%r)." % (name, self)) 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] = value[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._children[0]._blocking = False else: for child in self.__parallel._children[1:]: child._blocking = False self.__parallel = None if len(WidgetState.layout_stack): WidgetState.layout_stack.pop() return ret
self.__buttons.extend(iter_nested_buttons(self.__parallel)) self.__parallel = None return ret if __name__ == '__main__': from experiment import Experiment from state import Wait, Loop, Parallel, Meanwhile, UntilDone, Serial from math import sin, cos from contextlib import nested exp = Experiment(background_color="#330000") Wait(2.0) with Parallel(): slider = Slider(min=exp.screen.left, max=exp.screen.right, duration=5.0) rect = Rectangle(color="purple", width=50, height=50, center_top=exp.screen.left_top, duration=5.0) with Meanwhile(): rect.animate(center_x=lambda t, initial: slider.value) ti = TextInput(text="EDIT!", duration=5.0) Wait(until=ti.text) Label(text=ti.text, duration=5.0, font_size=50, color="white")