def draw_cal_target(self, x, y=None, pump_events=True): fill() if pump_events: pump() if y is None: y = x[1] x = x[0] blit(self.dc_target, 5, (int(x), int(y))) flip()
def record_saccades(self): # Following code a rehashing of code borrowed from John Christie's original code # Get & write time of target onset target_onset = self.el.now() self.el.write("TARGET_ON %d" % target_onset) # Until 2500ms post target onset, or until target fixated while self.el.now() - 2500 and not self.target_acquired: self.display_refresh(target=True) pump() # Get end point of saccades made queue = self.el.get_event_queue([EL_SACCADE_END]) # Check to see if saccade was made to target for saccade in queue: # Get end point of saccade gaze = saccade.getEndGaze() # Check if gaze fell outside fixation boundary if lsl(gaze, P.screen_c) > self.gaze_boundary: # Get distance between gaze and target dist_from_target = lsl(gaze, self.target_loc) # Log if saccade is inside or outside boundary around target accuracy = SACC_OUTSIDE if dist_from_target > self.gaze_boundary else SACC_INSIDE # If more than one saccade if len(self.saccades): # Grab duration of saccade, relative to the previous saccade # Not entirely sure why 4 is added.... duration = saccade.getStartTime() + 4 - self.saccades[-1]['end_time'] # Otherwise, get duration of saccade relative to target onset else: duration = saccade.getStartTime() + 4 - target_onset # Write saccade info to database if len(self.saccades) < 3: self.saccades.append({ "rt": saccade.getStartTime() - target_onset, "accuracy": accuracy, "dist_from_target": dist_from_target, "start_x": saccade.getStartGaze()[0], "start_y": saccade.getStartGaze()[1], "end_x": saccade.getEndGaze()[0], "end_y": saccade.getEndGaze()[1], "end_time": saccade.getEndTime(), "duration": duration }) # Target found = True if gaze within boundary surrounding target if dist_from_target <= self.gaze_boundary: self.target_acquired = True break
def start_trial_button(self): fill() blit(self.next_trial_box, 5, self.next_trial_button_loc, flip_x=P.flip_x) blit(self.next_trial_msg, 5, self.next_trial_button_loc, flip_x=P.flip_x) flip() if P.demo_mode or P.dm_always_show_cursor: show_mouse_cursor() flush() clicked = False while not clicked: event_queue = pump(True) for e in event_queue: if e.type == SDL_MOUSEBUTTONDOWN: clicked = self.within_boundary("next trial button", [e.button.x, e.button.y]) elif e.type == SDL_KEYDOWN: ui_request(e.key.keysym) if not (P.demo_mode or P.dm_always_show_cursor): hide_mouse_cursor()
def get_effort(self): slider_loc = (P.screen_c[0], int(P.screen_y * 0.55)) slider_cols = {'line': WHITE, 'slider': TRANSLUCENT_WHITE} scale = Slider(int(P.screen_x * 0.75), ticks=5, location=slider_loc, fills=slider_cols) label_pad = scale.tick.surface_height show_mouse_cursor() onset = time.time() while True: sq = pump(True) ui_request(queue=sq) fill() blit(self.effort_q, 5, (P.screen_c[0], int(P.screen_y * 0.3))) blit(self.mineffort_msg, 8, (scale.xmin, slider_loc[1] + label_pad)) blit(self.maxeffort_msg, 8, (scale.xmax, slider_loc[1] + label_pad)) scale.draw() if scale.pos != None: self.submit.draw() flip() scale.listen(sq) if scale.pos != None: if self.submit.listen(sq) or key_pressed('Return', queue=sq): rt = time.time() - onset hide_mouse_cursor() return (scale.pos, rt)
def __trial__(self, trial, practice): """ Private method; manages a trial. """ from klibs.KLUtilities import pump, show_mouse_cursor, hide_mouse_cursor # At start of every trial, before setup_response_collector or trial_prep are run, retrieve # the values of the independent variables (factors) for that trial (as generated earlier by # TrialFactory) and set them as attributes of the experiment object. factors = list(self.trial_factory.exp_factors.keys()) for iv in factors: iv_value = trial[factors.index(iv)] setattr(self, iv, iv_value) pump() self.setup_response_collector() self.trial_prep() tx = None try: if P.development_mode and (P.dm_trial_show_mouse or (P.eye_tracking and not P.eye_tracker_available)): show_mouse_cursor() self.evm.start_clock() if P.eye_tracking and not P.manual_eyelink_recording: self.el.start(P.trial_number) P.in_trial = True self.__log_trial__(self.trial()) P.in_trial = False if P.eye_tracking and not P.manual_eyelink_recording: self.el.stop() if P.development_mode and (P.dm_trial_show_mouse or (P.eye_tracking and not P.eye_tracker_available)): hide_mouse_cursor() self.evm.stop_clock() self.trial_clean_up() except TrialException as e: P.trial_id = False self.trial_clean_up() self.evm.stop_clock() tx = e if P.eye_tracking and not P.manual_eyelink_recording: # todo: add a warning, here, if the recording hasn't been stopped when under manual control self.el.stop() if tx: raise tx
def jc_saccade_data(self): # following code is tidied up but otherwise borrowed from John Christie's original code target_onset = self.el.now() self.el.write("TARGETON %d" % target_onset) while self.el.now() - target_onset < 2500 and not self.target_acquired: self.display_refresh(self.box_axis_during_target(), self.circle, target=self.target_location) pump() # refreshes TryLink event queue if using queue = self.el.get_event_queue([EL_SACCADE_END]) for saccade in queue: gaze = saccade.getEndGaze() if lsl(gaze, P.screen_c) > self.fixation_boundary: dist_from_target = lsl( gaze, self.target_locs[self.target_location]) accuracy = SACC_OUTSIDE if dist_from_target > self.fixation_boundary else SACC_INSIDE if len(self.saccades): duration = saccade.getStartTime( ) + 4 - self.saccades[-1]['end_time'] else: duration = saccade.getStartTime() + 4 - target_onset if len(self.saccades) < 3: self.saccades.append({ "rt": saccade.getStartTime() - target_onset, "accuracy": accuracy, "dist_from_target": dist_from_target, "start_x": saccade.getStartGaze()[0], "start_y": saccade.getStartGaze()[1], "end_x": saccade.getEndGaze()[0], "end_y": saccade.getEndGaze()[1], "end_time": saccade.getEndTime(), "duration": duration }) if dist_from_target <= self.fixation_boundary: self.target_acquired = True break
def jc_wait_time(self): if self.before_target: if lsl(self.el.gaze(), P.screen_c) > self.fixation_boundary: self.log_and_recycle_trial('eye') q = pump(True) if key_pressed(queue=q): if key_pressed(SDLK_SPACE, queue=q): self.log_and_recycle_trial('early') else: self.log_and_recycle_trial('key')
def key_pressed(self, keysym, queue=None): pressed = False if not queue: queue = pump(True) for e in queue: if e.type == SDL_KEYDOWN: ui_request(e.key.keysym) if e.key.keysym.sym == keysym: pressed = True break return pressed
def _collect(self): q = pump(True) ui_request(queue=q) for e in q: if e.type == sdl2.SDL_MOUSEBUTTONDOWN: coords = (e.button.x, e.button.y) response = self.which_boundary(coords) if response != None: return response return None
def wait_time(self): # Appropriated verbatim from original code written by John Christie if self.before_target: gaze = self.el.gaze() if not self.bi.within_boundary(label='drift_correct', p=gaze): self.log_and_recycle_trial('eye') q = pump(True) if key_pressed(queue=q): if key_pressed(SDLK_SPACE, queue=q): self.log_and_recycle_trial('early') else: self.log_and_recycle_trial('key')
def drift_correct(self, location=None, target=None, fill_color=None, draw_target=True): """Checks the accuracy of the eye tracker's calibration by presenting a fixation stimulus and requiring the participant to press the space bar while looking directly at it. If there is a large difference between the gaze location at the time the key was pressed and the true location of the fixation, it indicates that there has been drift in the calibration. In TryLink mode, drift correct targets are still displayed the same as with a hardware eye tracker. Simulated drift corrects are performed by clicking the drift correct target with the mouse. Args: location (Tuple(int, int), optional): The (x,y) pixel coordinates where the drift correct target should be located. Defaults to the center of the screen. target: A :obj:`Drawbject` or other :func:`KLGraphics.blit`-able shape to use as the drift correct target. Defaults to a circular :func:`drift_correct_target`. fill_color: A :obj:`List` or :obj:`Tuple` containing an RGBA colour to use for the background for the drift correct screen. Defaults to the value of ``P.default_fill_color``. draw_target (bool, optional): A flag indicating whether the function should draw the drift correct target itself (True), or whether it should leave it to the programmer to draw the target before :meth:`drift_correct` is called (False). Defaults to True. """ show_mouse_cursor() target = drift_correct_target() if target is None else target draw_target = EL_TRUE if draw_target in [EL_TRUE, True] else EL_FALSE location = P.screen_c if location is None else location if not iterable(location): raise ValueError("'location' must be a pair of (x,y) pixel coordinates.") dc_boundary = CircleBoundary('drift_correct', location, P.screen_y // 30) while True: event_queue = pump(True) ui_request(queue=event_queue) if draw_target == EL_TRUE: fill(P.default_fill_color if not fill_color else fill_color) blit(target, 5, location) flip() else: SDL_Delay(2) # required for pump() to reliably return mousebuttondown events for e in event_queue: if e.type == SDL_MOUSEBUTTONDOWN and dc_boundary.within([e.button.x, e.button.y]): hide_mouse_cursor() if draw_target == EL_TRUE: fill(P.default_fill_color if not fill_color else fill_color) flip() return 0
def slide(self): show_mouse_cursor() self.blit() dragging = False while True: if not dragging: m_pos = mouse_pos() for event in pump(True): if event.type == sdl2.SDL_KEYDOWN: ui_request(event.key.keysym) elif event.type in (sdl2.SDL_MOUSEBUTTONDOWN, sdl2.SDL_MOUSEBUTTONUP): within_button = self.within_boundary("button", m_pos) if self.button_active and within_button: return self.response dragging = self.within_boundary("handle", m_pos) if dragging: button_up = False off_handle = False for event in pump(True): if event.type == sdl2.SDL_KEYDOWN: ui_request(event.key.keysym) elif event.type == sdl2.SDL_MOUSEBUTTONUP: button_up = True off_handle = not self.within_boundary("handle", mouse_pos()) if off_handle or button_up: dragging = False self.response = self.handle_value() self.button_active = True flush() return -1 self.handle_pos = mouse_pos()[0] self.blit() return False
def key_pressed(key=None, queue=None): """Checks an event queue to see if a given key has been pressed. If no key is specified, the function will return True if any key has been pressed. If an event queue is not manually specified, :func:`~klibs.KLUtilities.pump` will be called and the returned event queue will be used. For a comprehensive list of valid key names, see the 'Name' column of the following table: https://wiki.libsdl.org/StuartPBentley/CombinedKeyTable For a comprehensive list of valid SDL keycodes, consult the following table: https://wiki.libsdl.org/SDL_Keycode Args: key (str or :obj:`sdl2.SDL_Keycode`, optional): The key name or SDL keycode corresponding to the key to check. If not specified, any keypress will return True. queue (:obj:`List` of :obj:`sdl2.SDL_Event`, optional): A list of SDL_Events to check for valid keypress events. Returns: bool: True if key has been pressed, otherwise False. Raises: ValueError: If the keycode is anything other than an SDL_Keycode integer or None. """ strtypes = [type(u" "), type(" ")] # for Python 2/3 unicode compatibility if type(key) in strtypes: keycode = SDL_GetKeyFromName(key.encode('utf8')) if keycode == 0: raise ValueError("'{0}' is not a recognized key name.".format(key)) else: keycode = key if type(keycode).__name__ not in ['int', 'NoneType']: raise ValueError( "'key' must be a string, an SDL Keycode (int), or None.") pressed = False if queue == None: queue = pump(True) for e in queue: if e.type == SDL_KEYDOWN: ui_request(e.key.keysym) if not keycode or e.key.keysym.sym == keycode: pressed = True return pressed
def collect_response(self): self.start = time.time() finished = False selection = None last_selected = None flush() mt_start = None while not finished: show_mouse_cursor() events = pump(True) for e in events: if e.type == sdl2.SDL_KEYDOWN: ui_request(e.key.keysym) elif e.type == sdl2.SDL_MOUSEBUTTONDOWN: selection = None for b in self.buttons: if self.within_boundary(b.button_text, [e.button.x, e.button.y]): self.toggle(b) if not self.rt: self.rt = time.time() - self.start mt_start = time.time() if b.active: selection = b last_selected = b if callable(b.callback): if self.finish_b is None: return b.callback else: b.callback() try: if self.finish_b.active and self.within_boundary( "Done", [e.button.x, e.button.y]): self.response = int(last_selected.button_text) self.mt = time.time() - mt_start finished = True except AttributeError: pass try: self.finish_b.active = selection is not None except AttributeError: pass self.render() fill() flip() hide_mouse_cursor()
def konami_code(callback=None, cb_args={}, queue=None): """An implementation of the classic Konami code. If called repeatedly within a loop, this function will collect keypress matching the sequence and save them between calls until the full sequence has been entered correctly. If a callback function has been specified, it will be called once the code has been entered. If any incorrect keys are pressed during entry, the collected input so far will be reset and the code will need to be entered again from the start. Useful for adding hidden debug menus and other things you really don't want participants activating by mistake...? Args: callback (function, optional): The function to be run upon successful input of the Konami code. cbargs (:obj:`Dict`, optional): A dict of keyword arguments to pass to the callback function when it's called. queue (:obj:`List` of :obj:`sdl2.SDL_Event`, optional): A list of SDL Events to check for valid keys in the sequence. Returns: bool: True if sequence was correctly entered, otherwise False. """ sequence = [ SDLK_UP, SDLK_DOWN, SDLK_UP, SDLK_DOWN, SDLK_LEFT, SDLK_RIGHT, SDLK_LEFT, SDLK_RIGHT, SDLK_b, SDLK_a ] if not hasattr(konami_code, "input"): konami_code.input = [ ] # static variable, stays with the function between calls if queue == None: queue = pump(True) for e in queue: if e.type == SDL_KEYDOWN: ui_request(e.key.keysym) konami_code.input.append(e.key.keysym.sym) if konami_code.input != sequence[:len(konami_code.input)]: konami_code.input = [] # reset input if mismatch encountered elif len(konami_code.input) == len(sequence): konami_code.input = [] if callable(callback): callback(**cb_args) return True return False
def get_input_key(self): keys = [] for event in pump(True): if event.type == sdl2.SDL_KEYDOWN: keysym = event.key.keysym if not self.el._quitting: # don't process quit requests while already quitting ui_request(keysym) try: key = self.pylink_keycodes[keysym.sym] except KeyError: key = keysym.sym # don't allow escape to control tracker unless calibrating if key == pylink.ESC_KEY and not self.el.in_setup: key = pylink.JUNK_KEY keys.append(pylink.KeyInput(key, keysym.mod)) return keys
def collect(self): show_mouse_cursor() onset = time.time() while self.scale.response == None: q = pump(True) ui_request(queue=q) fill() blit(self.q, location=self.origin, registration=8) self.scale.response_listener(q) flip() response = self.scale.response rt = time.time() - onset hide_mouse_cursor() self.scale.response = None # reset for next time return Response(response, rt)
def any_key(allow_mouse_click=True): """A function that waits until any keyboard (or mouse, if enabled) input is received before returning. Intended for use in situations when you want to require input before progressing through the experiment (e.g. "To start the next block, press any key..."). Not to be used for response collection (see :mod:`~klibs.KLResponseCollectors`). Args: allow_mouse_click (bool, optional): Whether to return immediately on mouse clicks in addition to key presses. """ any_key_pressed = False while not any_key_pressed: for event in pump(True): if event.type == SDL_KEYDOWN: ui_request(event.key.keysym) any_key_pressed = True if event.type == SDL_MOUSEBUTTONUP and allow_mouse_click: any_key_pressed = True
def __validate(self): instruction = ( "Okay, threshold set! " "To ensure its validity, please provide one (and only one) more response." ) fill() message(instruction, location=P.screen_c, registration=5) flip() self.stream.start() validate_counter = CountDown(5) while validate_counter.counting(): ui_request() if self.stream.sample().peak >= self.threshold: validate_counter.finish() self.threshold_valid = True self.stream.stop() if self.threshold_valid: validation_msg = "Great, validation was successful! Press any key to continue." else: validation_msg = ( "Validation wasn't successful. " "Type C to re-calibrate or V to try validation again.") fill() message(validation_msg, location=P.screen_c, registration=5) flip() selection_made = False while not selection_made: q = pump(True) if self.threshold_valid: if key_pressed(queue=q): return else: if key_pressed(SDLK_c, queue=q): self.calibrate() elif key_pressed(SDLK_v, queue=q): self.__validate()
def ui_request(key_press=None, execute=True, queue=None): """Checks keyboard input for interface commands, which currently include: - Quit (Ctrl/Command-Q): Quit the experiment runtime - Calibrate Eye Tracker (Ctrl/Command-C): Enter setup mode for the connected eye tracker, if eye tracking is enabled for the experiment and not using TryLink simulation. If no event queue from :func:`~klibs.KLUtilities.pump` and no keypress event(s) are supplied to this function, the current contents of the SDL2 event queue will be fetched and processed using :func:`~klibs.KLUtilities.pump`. This function is meant to be called during loops in your experiment where no other input checking occurs, to ensure that you can quit your experiment or recalibrate your eye tracker during those periods. This function is automatically called by other functions that process keyboard/mouse input, such as :func:`any_key` and :func:`key_pressed`, so you will not need to call it yourself in places where one of them is already being called. In addition, the :obj:`~klibs.KLResponseCollectors.ResponseCollector` collect method also calls this function every loop, meaning that you do not need to include it when writing ResponseCollector callbacks. Args: key_press (:obj:`sdl2.SDL_Keysym`, optional): The key.keysym of an SDL_KEYDOWN event to check for a valid UI command. execute (bool, optional): If True, valid UI commands will be executed immediately. Otherwise, valid UI commands will return a string indicating the type of command received. Defaults to True. queue (:obj:`List` of :obj:`sdl2.SDL_Event`, optional): A list of SDL Events to check for valid UI commands. Returns: str or bool: "quit" if a Quit request encountered, "el_calibrate" if a Calibrate Eye Tracker request encountered, otherwise False. """ if key_press == None: if queue == None: queue = pump(True) for e in queue: if e.type == SDL_KEYDOWN: request = ui_request(e.key.keysym, execute) if request: return request return False else: try: key_press.mod except AttributeError: wrong = type(key_press).__name__ e = "'key_press' must be a valid SDL Keysym object (got '{0}')".format( wrong) raise TypeError(e) k = key_press if any(k.mod & mod for mod in [KMOD_GUI, KMOD_CTRL]): # if ctrl or meta being held if k.sym == SDLK_q: if execute: from klibs.KLEnvironment import exp exp.quit() return "quit" elif k.sym == SDLK_c: if P.eye_tracking: from klibs.KLEnvironment import el if el.initialized: # make sure el.setup() has been run already if execute: el.calibrate() return "el_calibrate" return False
def query(query_ob, anonymous=False): '''Asks the user a question and collects the response via the keyboard. Intended for use with the queries contained within a project's user_queries.json file. This function is used internally for collecting demographics at the start of each run, but can also be used during experiment runtime to collect info from participants based on the queries contained in the "experimental" section of the user_queries.json file. Args: query_ob (:class:`~klibs.KLJSON_Object.AttributeDict`): The object containing the query to present. See :obj:`~klibs.KLCommunication.user_queries` for more information. anonymous (bool, optional): If True, will immediately return the query object's anonymous value without prompting the user (used interally for P.development_mode). Defaults to False. Returns: The response to the query, coerced to the type specified by query_ob.format.type (can be str, int, float, bool, or None). Raises: ValueError: If the query object's type is not one of "str", "int", "float", "bool", or None, or if a query_ob.format.range value is given and the type is not "int" or "float". TypeError: If query_ob.accepted is specified and it is not a list of values, or if a query_ob.format.range is specified and it is not a two-item list. ''' from klibs.KLEnvironment import txtm if anonymous: try: # Check if anon value is an EVAL statement, and if so evaluate it eval_statement = re.match(re.compile(u"^EVAL:[ ]*(.*)$"), query_ob.anonymous_value) if eval_statement: query_ob.anonymous_value = eval(eval_statement.group(1)) except TypeError: pass return query_ob.anonymous_value f = query_ob.format if f.type not in ("int", "float", "str", "bool", None): err = "Invalid data type for query '{0}': {1}".format( query_ob.title, f.type) raise ValueError(err) # Set defaults for styles and positioning if not specified if f.styles == 'default': f.styles = AttributeDict({ 'query': 'default', 'input': 'default', 'error': 'alert' }) locations = AttributeDict({ 'query': AUTO_POS, 'input': AUTO_POS, 'error': AUTO_POS }) registrations = AttributeDict({ 'query': AUTO_POS, 'input': AUTO_POS, 'error': AUTO_POS }) if f.positions == 'default': f.positions = AttributeDict({ 'locations': locations, 'registrations': registrations }) else: if f.positions.locations == 'default': f.positions.locations = locations if f.positions.registrations == 'default': f.positions.registrations = registrations q_text = message(query_ob.query, f.styles.query, align='center', blit_txt=False) # address automatic positioning p = f.positions if p.locations.query == AUTO_POS: p.locations.query = [P.screen_c[0], int(0.1 * P.screen_y)] p.registrations.query = BL_CENTER for k in ['input', 'error']: if p.locations[k] == AUTO_POS: v_pad = q_text.height + 2 * txtm.styles[f.styles.query].line_height p.locations[k] = [P.screen_c[0], p.locations.query[1] + v_pad] p.registrations[k] = BL_CENTER # Create an informative error message for invalid responses accepted_responses = query_ob.accepted # for code readability try: if accepted_responses: try: iter(accepted_responses) accepted_str = pretty_list(accepted_responses) invalid_answer_str = default_strings['invalid_answer'].format( accepted_str) except: raise TypeError( "The 'accepted' key of a question must be a list of values." ) elif f.range: if f.type not in ("int", "float"): raise ValueError( "Only queries with numeric types can use the range parameter." ) elif isinstance(f.range, list) == False or len(f.range) != 2: raise TypeError( "Query ranges must be two-item lists, containing an upper bound " "and a lower bound.") try: template = default_strings['out_of_range'] except KeyError: template = "Your answer must be a number between {0} and {1}, inclusive." invalid_answer_str = template.format(f.range[0], f.range[1]) except: cso("\n<red>Error encountered while parsing query '{0}':</red>".format( query_ob.title)) raise # user input loop; exited by breaking input_string = u'' # populated in loop below error_string = None user_finished = False # Clear event queue and draw query text to screen before entering input loop flush() SDL_StartTextInput() fill() blit(q_text, p.registrations.query, p.locations.query) flip() while not user_finished: for event in pump(True): if event.type == SDL_KEYDOWN: error_string = None # clear error string (if any) on new key event ui_request(event.key.keysym) sdl_keysym = event.key.keysym.sym if sdl_keysym == SDLK_ESCAPE: # Esc clears any existing input input_string = "" elif sdl_keysym == SDLK_BACKSPACE: # Backspace removes last character from input input_string = input_string[:-1] elif sdl_keysym in (SDLK_KP_ENTER, SDLK_RETURN): # Enter or Return check if a valid response has been made and end loop if it has if len(input_string) > 0: response = input_string # If type is 'int' or 'float', make sure input can be converted to that type if f.type == "int": try: response = int(input_string) except ValueError: error_string = "Please respond with an integer." elif f.type == "float": try: response = float(input_string) except ValueError: error_string = "Please respond with a number." # If no errors yet, check input against list of accepted values (if q has one) if not error_string: if accepted_responses: user_finished = response in accepted_responses if not user_finished: error_string = invalid_answer_str elif f.range: user_finished = (f.range[0] <= response <= f.range[1]) if not user_finished: error_string = invalid_answer_str else: user_finished = True elif query_ob.allow_null is True: user_finished = True else: # If no input and allow_null is false, display error error_string = default_strings['answer_not_supplied'] elif event.type == SDL_TEXTINPUT: input_string += event.text.text.decode('utf-8') if f.case_sensitive is False: input_string = input_string.lower() input_string = input_string.strip( ) # remove any trailing whitespace else: continue # If any text entered or error message encountered, render text for drawing if error_string: rendered_input = message(error_string, f.styles.error, blit_txt=False) input_string = "" elif len(input_string): if f.password: rendered_input = message(len(input_string) * '*', f.styles.input, blit_txt=False) else: rendered_input = message(input_string, f.styles.input, blit_txt=False) else: rendered_input = None # Draw question and any entered response to screen fill() blit(q_text, p.registrations.query, p.locations.query) if rendered_input: loc = p.locations.error if error_string else p.locations.input reg = p.registrations.error if error_string else p.registrations.input blit(rendered_input, reg, loc) flip() # Once a valid response has been made, clear the screen fill() flip() SDL_StopTextInput() if query_ob.allow_null and len(input_string) == 0: return None elif f.type == "int": return int(input_string) elif f.type == "str": if f.action == QUERY_ACTION_HASH: return make_hash(input_string) elif f.action == QUERY_ACTION_UPPERCASE: return utf8(input_string).upper() else: return utf8(input_string) elif f.type == "float": return float(input_string) elif f.type == "bool": return input_string in f.accept_as_true else: return input_string