def __init__(self, parent: ba.Widget, configkey: str, position: Tuple[float, float], size: Tuple[float, float], displayname: Union[str, ba.Lstr] = None, scale: float = None, maxwidth: float = None, autoselect: bool = True, value_change_call: Callable[[Any], Any] = None): if displayname is None: displayname = configkey self._value_change_call = value_change_call self._configkey = configkey self.widget = ba.checkboxwidget( parent=parent, autoselect=autoselect, position=position, size=size, text=displayname, textcolor=(0.8, 0.8, 0.8), value=ba.app.config.resolve(configkey), on_value_change_call=self._value_changed, scale=scale, maxwidth=maxwidth) # complain if we outlive our checkbox ba.uicleanupcheck(self, self.widget)
def make_radio_group(check_boxes: Sequence[ba.Widget], value_names: Sequence[str], value: str, value_change_call: Callable[[str], Any]) -> None: """Link the provided check_boxes together into a radio group.""" def _radio_press(check_string: str, other_check_boxes: List[ba.Widget], val: int) -> None: if val == 1: value_change_call(check_string) for cbx in other_check_boxes: ba.checkboxwidget(edit=cbx, value=False) for i, check_box in enumerate(check_boxes): ba.checkboxwidget(edit=check_box, value=(value == value_names[i]), is_radio_button=True, on_value_change_call=ba.Call( _radio_press, value_names[i], [c for c in check_boxes if c != check_box]))
def __init__(self, gametype: Type[ba.GameActivity], sessiontype: Type[ba.Session], config: Optional[Dict[str, Any]], completion_call: Callable[[Optional[Dict[str, Any]]], Any], default_selection: str = None, transition: str = 'in_right', edit_info: Dict[str, Any] = None): # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-locals from ba.internal import (get_unowned_maps, get_filtered_map_name, get_map_class, get_map_display_string) self._gametype = gametype self._sessiontype = sessiontype # If we're within an editing session we get passed edit_info # (returning from map selection window, etc). if edit_info is not None: self._edit_info = edit_info # ..otherwise determine whether we're adding or editing a game based # on whether an existing config was passed to us. else: if config is None: self._edit_info = {'editType': 'add'} else: self._edit_info = {'editType': 'edit'} self._r = 'gameSettingsWindow' valid_maps = gametype.get_supported_maps(sessiontype) if not valid_maps: ba.screenmessage(ba.Lstr(resource='noValidMapsErrorText')) raise Exception('No valid maps') self._settings_defs = gametype.get_available_settings(sessiontype) self._completion_call = completion_call # To start with, pick a random map out of the ones we own. unowned_maps = get_unowned_maps() valid_maps_owned = [m for m in valid_maps if m not in unowned_maps] if valid_maps_owned: self._map = valid_maps[random.randrange(len(valid_maps_owned))] # Hmmm.. we own none of these maps.. just pick a random un-owned one # I guess.. should this ever happen? else: self._map = valid_maps[random.randrange(len(valid_maps))] is_add = (self._edit_info['editType'] == 'add') # If there's a valid map name in the existing config, use that. try: if (config is not None and 'settings' in config and 'map' in config['settings']): filtered_map_name = get_filtered_map_name( config['settings']['map']) if filtered_map_name in valid_maps: self._map = filtered_map_name except Exception: ba.print_exception('Error getting map for editor.') if config is not None and 'settings' in config: self._settings = config['settings'] else: self._settings = {} self._choice_selections: Dict[str, int] = {} uiscale = ba.app.ui.uiscale width = 720 if uiscale is ba.UIScale.SMALL else 620 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 height = (365 if uiscale is ba.UIScale.SMALL else 460 if uiscale is ba.UIScale.MEDIUM else 550) spacing = 52 y_extra = 15 y_extra2 = 21 map_tex_name = (get_map_class(self._map).get_preview_texture_name()) if map_tex_name is None: raise Exception('no map preview tex found for' + self._map) map_tex = ba.gettexture(map_tex_name) top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 super().__init__(root_widget=ba.containerwidget( size=(width, height + top_extra), transition=transition, scale=(2.19 if uiscale is ba.UIScale.SMALL else 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -17) if uiscale is ba.UIScale.SMALL else (0, 0))) btn = ba.buttonwidget( parent=self._root_widget, position=(45 + x_inset, height - 82 + y_extra2), size=(180, 70) if is_add else (180, 65), label=ba.Lstr(resource='backText') if is_add else ba.Lstr( resource='cancelText'), button_type='back' if is_add else None, autoselect=True, scale=0.75, text_scale=1.3, on_activate_call=ba.Call(self._cancel)) ba.containerwidget(edit=self._root_widget, cancel_button=btn) add_button = ba.buttonwidget( parent=self._root_widget, position=(width - (193 + x_inset), height - 82 + y_extra2), size=(200, 65), scale=0.75, text_scale=1.3, label=ba.Lstr(resource=self._r + '.addGameText') if is_add else ba.Lstr( resource='doneText')) if ba.app.ui.use_toolbars: pbtn = _ba.get_special_widget('party_button') ba.widget(edit=add_button, right_widget=pbtn, up_widget=pbtn) ba.textwidget(parent=self._root_widget, position=(-8, height - 70 + y_extra2), size=(width, 25), text=gametype.get_display_string(), color=ba.app.ui.title_color, maxwidth=235, scale=1.1, h_align='center', v_align='center') map_height = 100 scroll_height = map_height + 10 # map select and margin # Calc our total height we'll need scroll_height += spacing * len(self._settings_defs) scroll_width = width - (86 + 2 * x_inset) self._scrollwidget = ba.scrollwidget(parent=self._root_widget, position=(44 + x_inset, 35 + y_extra), size=(scroll_width, height - 116), highlight=False, claims_left_right=True, claims_tab=True, selection_loops_to_parent=True) self._subcontainer = ba.containerwidget(parent=self._scrollwidget, size=(scroll_width, scroll_height), background=False, claims_left_right=True, claims_tab=True, selection_loops_to_parent=True) v = scroll_height - 5 h = -40 # Keep track of all the selectable widgets we make so we can wire # them up conveniently. widget_column: List[List[ba.Widget]] = [] # Map select button. ba.textwidget(parent=self._subcontainer, position=(h + 49, v - 63), size=(100, 30), maxwidth=110, text=ba.Lstr(resource='mapText'), h_align='left', color=(0.8, 0.8, 0.8, 1.0), v_align='center') ba.imagewidget( parent=self._subcontainer, size=(256 * 0.7, 125 * 0.7), position=(h + 261 - 128 + 128.0 * 0.56, v - 90), texture=map_tex, model_opaque=ba.getmodel('level_select_button_opaque'), model_transparent=ba.getmodel('level_select_button_transparent'), mask_texture=ba.gettexture('mapPreviewMask')) map_button = btn = ba.buttonwidget( parent=self._subcontainer, size=(140, 60), position=(h + 448, v - 72), on_activate_call=ba.Call(self._select_map), scale=0.7, label=ba.Lstr(resource='mapSelectText')) widget_column.append([btn]) ba.textwidget(parent=self._subcontainer, position=(h + 363 - 123, v - 114), size=(100, 30), flatness=1.0, shadow=1.0, scale=0.55, maxwidth=256 * 0.7 * 0.8, text=get_map_display_string(self._map), h_align='center', color=(0.6, 1.0, 0.6, 1.0), v_align='center') v -= map_height for setting in self._settings_defs: value = setting.default value_type = type(value) # Now, if there's an existing value for it in the config, # override with that. try: if (config is not None and 'settings' in config and setting.name in config['settings']): value = value_type(config['settings'][setting.name]) except Exception: ba.print_exception() # Shove the starting value in there to start. self._settings[setting.name] = value name_translated = self._get_localized_setting_name(setting.name) mw1 = 280 mw2 = 70 # Handle types with choices specially: if isinstance(setting, ba.ChoiceSetting): for choice in setting.choices: if len(choice) != 2: raise ValueError( "Expected 2-member tuples for 'choices'; got: " + repr(choice)) if not isinstance(choice[0], str): raise TypeError( 'First value for choice tuple must be a str; got: ' + repr(choice)) if not isinstance(choice[1], value_type): raise TypeError( 'Choice type does not match default value; choice:' + repr(choice) + '; setting:' + repr(setting)) if value_type not in (int, float): raise TypeError( 'Choice type setting must have int or float default; ' 'got: ' + repr(setting)) # Start at the choice corresponding to the default if possible. self._choice_selections[setting.name] = 0 for index, choice in enumerate(setting.choices): if choice[1] == value: self._choice_selections[setting.name] = index break v -= spacing ba.textwidget(parent=self._subcontainer, position=(h + 50, v), size=(100, 30), maxwidth=mw1, text=name_translated, h_align='left', color=(0.8, 0.8, 0.8, 1.0), v_align='center') txt = ba.textwidget( parent=self._subcontainer, position=(h + 509 - 95, v), size=(0, 28), text=self._get_localized_setting_name(setting.choices[ self._choice_selections[setting.name]][0]), editable=False, color=(0.6, 1.0, 0.6, 1.0), maxwidth=mw2, h_align='right', v_align='center', padding=2) btn1 = ba.buttonwidget(parent=self._subcontainer, position=(h + 509 - 50 - 1, v), size=(28, 28), label='<', autoselect=True, on_activate_call=ba.Call( self._choice_inc, setting.name, txt, setting, -1), repeat=True) btn2 = ba.buttonwidget(parent=self._subcontainer, position=(h + 509 + 5, v), size=(28, 28), label='>', autoselect=True, on_activate_call=ba.Call( self._choice_inc, setting.name, txt, setting, 1), repeat=True) widget_column.append([btn1, btn2]) elif isinstance(setting, (ba.IntSetting, ba.FloatSetting)): v -= spacing min_value = setting.min_value max_value = setting.max_value increment = setting.increment ba.textwidget(parent=self._subcontainer, position=(h + 50, v), size=(100, 30), text=name_translated, h_align='left', color=(0.8, 0.8, 0.8, 1.0), v_align='center', maxwidth=mw1) txt = ba.textwidget(parent=self._subcontainer, position=(h + 509 - 95, v), size=(0, 28), text=str(value), editable=False, color=(0.6, 1.0, 0.6, 1.0), maxwidth=mw2, h_align='right', v_align='center', padding=2) btn1 = ba.buttonwidget(parent=self._subcontainer, position=(h + 509 - 50 - 1, v), size=(28, 28), label='-', autoselect=True, on_activate_call=ba.Call( self._inc, txt, min_value, max_value, -increment, value_type, setting.name), repeat=True) btn2 = ba.buttonwidget(parent=self._subcontainer, position=(h + 509 + 5, v), size=(28, 28), label='+', autoselect=True, on_activate_call=ba.Call( self._inc, txt, min_value, max_value, increment, value_type, setting.name), repeat=True) widget_column.append([btn1, btn2]) elif value_type == bool: v -= spacing ba.textwidget(parent=self._subcontainer, position=(h + 50, v), size=(100, 30), text=name_translated, h_align='left', color=(0.8, 0.8, 0.8, 1.0), v_align='center', maxwidth=mw1) txt = ba.textwidget( parent=self._subcontainer, position=(h + 509 - 95, v), size=(0, 28), text=ba.Lstr(resource='onText') if value else ba.Lstr( resource='offText'), editable=False, color=(0.6, 1.0, 0.6, 1.0), maxwidth=mw2, h_align='right', v_align='center', padding=2) cbw = ba.checkboxwidget(parent=self._subcontainer, text='', position=(h + 505 - 50 - 5, v - 2), size=(200, 30), autoselect=True, textcolor=(0.8, 0.8, 0.8), value=value, on_value_change_call=ba.Call( self._check_value_change, setting.name, txt)) widget_column.append([cbw]) else: raise Exception() # Ok now wire up the column. try: # pylint: disable=unsubscriptable-object prev_widgets: Optional[List[ba.Widget]] = None for cwdg in widget_column: if prev_widgets is not None: # Wire our rightmost to their rightmost. ba.widget(edit=prev_widgets[-1], down_widget=cwdg[-1]) ba.widget(cwdg[-1], up_widget=prev_widgets[-1]) # Wire our leftmost to their leftmost. ba.widget(edit=prev_widgets[0], down_widget=cwdg[0]) ba.widget(cwdg[0], up_widget=prev_widgets[0]) prev_widgets = cwdg except Exception: ba.print_exception( 'Error wiring up game-settings-select widget column.') ba.buttonwidget(edit=add_button, on_activate_call=ba.Call(self._add)) ba.containerwidget(edit=self._root_widget, selected_child=add_button, start_button=add_button) if default_selection == 'map': ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget) ba.containerwidget(edit=self._subcontainer, selected_child=map_button)
def __init__(self) -> None: from ba.internal import get_remote_app_name self._r = 'connectMobileDevicesWindow' width = 700 height = 390 spacing = 40 super().__init__(root_widget=ba.containerwidget( size=(width, height), transition='in_right', scale=(1.85 if ba.app.small_ui else 1.3 if ba.app.med_ui else 1.0), stack_offset=(-10, 0) if ba.app.small_ui else (0, 0))) btn = ba.buttonwidget(parent=self._root_widget, position=(40, height - 67), size=(140, 65), scale=0.8, label=ba.Lstr(resource='backText'), button_type='back', text_scale=1.1, autoselect=True, on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=btn) ba.textwidget(parent=self._root_widget, position=(width * 0.5, height - 42), size=(0, 0), text=ba.Lstr(resource=self._r + '.titleText'), maxwidth=370, color=ba.app.title_color, scale=0.8, h_align='center', v_align='center') ba.buttonwidget(edit=btn, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) v = height - 70.0 v -= spacing * 1.2 ba.textwidget(parent=self._root_widget, position=(15, v - 26), size=(width - 30, 30), maxwidth=width * 0.95, color=(0.7, 0.9, 0.7, 1.0), scale=0.8, text=ba.Lstr(resource=self._r + '.explanationText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')), ('${REMOTE_APP_NAME}', get_remote_app_name())]), max_height=100, h_align='center', v_align='center') v -= 90 # hmm the itms:// version doesnt bounce through safari but is kinda # apple-specific-ish # Update: now we just show link to the remote webpage. ba.textwidget(parent=self._root_widget, position=(width * 0.5, v + 5), size=(0, 0), color=(0.7, 0.9, 0.7, 1.0), scale=1.4, text='bombsquadgame.com/remote', maxwidth=width * 0.95, max_height=60, h_align='center', v_align='center') v -= 30 ba.textwidget(parent=self._root_widget, position=(width * 0.5, v - 35), size=(0, 0), color=(0.7, 0.9, 0.7, 0.8), scale=0.65, text=ba.Lstr(resource=self._r + '.bestResultsText'), maxwidth=width * 0.95, max_height=height * 0.19, h_align='center', v_align='center') ba.checkboxwidget( parent=self._root_widget, position=(width * 0.5 - 150, v - 116), size=(300, 30), maxwidth=300, scale=0.8, value=not ba.app.config.resolve('Enable Remote App'), autoselect=True, text=ba.Lstr(resource='disableRemoteAppConnectionsText'), on_value_change_call=self._on_check_changed)
def __init__(self, sessiontype: Type[ba.Session], playlist: str, scale_origin: Tuple[float, float], delegate: Any = None): # FIXME: Tidy this up. # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-locals from ba.internal import (getclass, have_pro, get_default_teams_playlist, get_default_free_for_all_playlist, filter_playlist) from ba.internal import get_map_class from bastd.ui.playlist import PlaylistTypeVars self._r = 'gameListWindow' self._delegate = delegate self._pvars = PlaylistTypeVars(sessiontype) self._transitioning_out = False self._do_randomize_val = (ba.app.config.get( self._pvars.config_name + ' Playlist Randomize', 0)) self._sessiontype = sessiontype self._playlist = playlist self._width = 500.0 self._height = 330.0 - 50.0 # In teams games, show the custom names/colors button. if self._sessiontype is ba.DualTeamSession: self._height += 50.0 self._row_height = 45.0 # Grab our maps to display. model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') # Poke into this playlist and see if we can display some of its maps. map_textures = [] map_texture_entries = [] rows = 0 columns = 0 game_count = 0 scl = 0.35 c_width_total = 0.0 try: max_columns = 5 name = playlist if name == '__default__': if self._sessiontype is ba.FreeForAllSession: plst = get_default_free_for_all_playlist() elif self._sessiontype is ba.DualTeamSession: plst = get_default_teams_playlist() else: raise Exception('unrecognized session-type: ' + str(self._sessiontype)) else: try: plst = ba.app.config[self._pvars.config_name + ' Playlists'][name] except Exception: print('ERROR INFO: self._config_name is:', self._pvars.config_name) print( 'ERROR INFO: playlist names are:', list(ba.app.config[self._pvars.config_name + ' Playlists'].keys())) raise plst = filter_playlist(plst, self._sessiontype, remove_unowned=False, mark_unowned=True) game_count = len(plst) for entry in plst: mapname = entry['settings']['map'] maptype: Optional[Type[ba.Map]] try: maptype = get_map_class(mapname) except Exception: maptype = None if maptype is not None: tex_name = maptype.get_preview_texture_name() if tex_name is not None: map_textures.append(tex_name) map_texture_entries.append(entry) rows = (max(0, len(map_textures) - 1) // max_columns) + 1 columns = min(max_columns, len(map_textures)) if len(map_textures) == 1: scl = 1.1 elif len(map_textures) == 2: scl = 0.7 elif len(map_textures) == 3: scl = 0.55 else: scl = 0.35 self._row_height = 128.0 * scl c_width_total = scl * 250.0 * columns if map_textures: self._height += self._row_height * rows except Exception: ba.print_exception('error listing playlist maps') show_shuffle_check_box = game_count > 1 if show_shuffle_check_box: self._height += 40 # Creates our _root_widget. scale = (1.69 if ba.app.small_ui else 1.1 if ba.app.med_ui else 0.85) super().__init__(position=scale_origin, size=(self._width, self._height), scale=scale) playlist_name: Union[str, ba.Lstr] = (self._pvars.default_list_name if playlist == '__default__' else playlist) self._title_text = ba.textwidget(parent=self.root_widget, position=(self._width * 0.5, self._height - 89 + 51), size=(0, 0), text=playlist_name, scale=1.4, color=(1, 1, 1), maxwidth=self._width * 0.7, h_align='center', v_align='center') self._cancel_button = ba.buttonwidget( parent=self.root_widget, position=(25, self._height - 53), size=(50, 50), scale=0.7, label='', color=(0.42, 0.73, 0.2), on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), iconscale=1.2) h_offs_img = self._width * 0.5 - c_width_total * 0.5 v_offs_img = self._height - 118 - scl * 125.0 + 50 bottom_row_buttons = [] self._have_at_least_one_owned = False for row in range(rows): for col in range(columns): tex_index = row * columns + col if tex_index < len(map_textures): tex_name = map_textures[tex_index] h = h_offs_img + scl * 250 * col v = v_offs_img - self._row_height * row entry = map_texture_entries[tex_index] owned = not (('is_unowned_map' in entry and entry['is_unowned_map']) or ('is_unowned_game' in entry and entry['is_unowned_game'])) if owned: self._have_at_least_one_owned = True try: desc = getclass(entry['type'], subclassof=ba.GameActivity ).get_config_display_string(entry) if not owned: desc = ba.Lstr( value='${DESC}\n${UNLOCK}', subs=[ ('${DESC}', desc), ('${UNLOCK}', ba.Lstr( resource='unlockThisInTheStoreText')) ]) desc_color = (0, 1, 0) if owned else (1, 0, 0) except Exception: desc = ba.Lstr(value='(invalid)') desc_color = (1, 0, 0) btn = ba.buttonwidget( parent=self.root_widget, size=(scl * 240.0, scl * 120.0), position=(h, v), texture=ba.gettexture(tex_name if owned else 'empty'), model_opaque=model_opaque if owned else None, on_activate_call=ba.Call(ba.screenmessage, desc, desc_color), label='', color=(1, 1, 1), autoselect=True, extra_touch_border_scale=0.0, model_transparent=model_transparent if owned else None, mask_texture=mask_tex if owned else None) if row == 0 and col == 0: ba.widget(edit=self._cancel_button, down_widget=btn) if row == rows - 1: bottom_row_buttons.append(btn) if not owned: # Ewww; buttons don't currently have alpha so in this # case we draw an image over our button with an empty # texture on it. ba.imagewidget(parent=self.root_widget, size=(scl * 260.0, scl * 130.0), position=(h - 10.0 * scl, v - 4.0 * scl), draw_controller=btn, color=(1, 1, 1), texture=ba.gettexture(tex_name), model_opaque=model_opaque, opacity=0.25, model_transparent=model_transparent, mask_texture=mask_tex) ba.imagewidget(parent=self.root_widget, size=(scl * 100, scl * 100), draw_controller=btn, position=(h + scl * 70, v + scl * 10), texture=ba.gettexture('lock')) # Team names/colors. self._custom_colors_names_button: Optional[ba.Widget] if self._sessiontype is ba.DualTeamSession: y_offs = 50 if show_shuffle_check_box else 0 self._custom_colors_names_button = ba.buttonwidget( parent=self.root_widget, position=(100, 200 + y_offs), size=(290, 35), on_activate_call=ba.WeakCall(self._custom_colors_names_press), autoselect=True, textcolor=(0.8, 0.8, 0.8), label=ba.Lstr(resource='teamNamesColorText')) if not have_pro(): ba.imagewidget( parent=self.root_widget, size=(30, 30), position=(95, 202 + y_offs), texture=ba.gettexture('lock'), draw_controller=self._custom_colors_names_button) else: self._custom_colors_names_button = None # Shuffle. def _cb_callback(val: bool) -> None: self._do_randomize_val = val cfg = ba.app.config cfg[self._pvars.config_name + ' Playlist Randomize'] = self._do_randomize_val cfg.commit() if show_shuffle_check_box: self._shuffle_check_box = ba.checkboxwidget( parent=self.root_widget, position=(110, 200), scale=1.0, size=(250, 30), autoselect=True, text=ba.Lstr(resource=self._r + '.shuffleGameOrderText'), maxwidth=300, textcolor=(0.8, 0.8, 0.8), value=self._do_randomize_val, on_value_change_call=_cb_callback) # Show tutorial. try: show_tutorial = ba.app.config['Show Tutorial'] except Exception: show_tutorial = True def _cb_callback_2(val: bool) -> None: cfg = ba.app.config cfg['Show Tutorial'] = val cfg.commit() self._show_tutorial_check_box = ba.checkboxwidget( parent=self.root_widget, position=(110, 151), scale=1.0, size=(250, 30), autoselect=True, text=ba.Lstr(resource=self._r + '.showTutorialText'), maxwidth=300, textcolor=(0.8, 0.8, 0.8), value=show_tutorial, on_value_change_call=_cb_callback_2) # Grumble: current autoselect doesn't do a very good job # with checkboxes. if self._custom_colors_names_button is not None: for btn in bottom_row_buttons: ba.widget(edit=btn, down_widget=self._custom_colors_names_button) if show_shuffle_check_box: ba.widget(edit=self._custom_colors_names_button, down_widget=self._shuffle_check_box) ba.widget(edit=self._shuffle_check_box, up_widget=self._custom_colors_names_button) else: ba.widget(edit=self._custom_colors_names_button, down_widget=self._show_tutorial_check_box) ba.widget(edit=self._show_tutorial_check_box, up_widget=self._custom_colors_names_button) self._play_button = ba.buttonwidget( parent=self.root_widget, position=(70, 44), size=(200, 45), scale=1.8, text_res_scale=1.5, on_activate_call=self._on_play_press, autoselect=True, label=ba.Lstr(resource='playText')) ba.widget(edit=self._play_button, up_widget=self._show_tutorial_check_box) ba.containerwidget(edit=self.root_widget, start_button=self._play_button, cancel_button=self._cancel_button, selected_child=self._play_button) # Update now and once per second. self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) self._update()
def _rebuild_ui(self) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-locals from ba.internal import get_device_value # Clear existing UI. for widget in self._root_widget.get_children(): widget.delete() self._textwidgets: Dict[str, ba.Widget] = {} # If we were supplied with settings, we're a secondary joystick and # just operate on that. in the other (normal) case we make our own. if not self._is_secondary: # Fill our temp config with present values (for our primary and # secondary controls). self._settings = {} for skey in [ 'buttonJump', 'buttonJump_B', 'buttonPunch', 'buttonPunch_B', 'buttonBomb', 'buttonBomb_B', 'buttonPickUp', 'buttonPickUp_B', 'buttonStart', 'buttonStart_B', 'buttonStart2', 'buttonStart2_B', 'buttonUp', 'buttonUp_B', 'buttonDown', 'buttonDown_B', 'buttonLeft', 'buttonLeft_B', 'buttonRight', 'buttonRight_B', 'buttonRun1', 'buttonRun1_B', 'buttonRun2', 'buttonRun2_B', 'triggerRun1', 'triggerRun1_B', 'triggerRun2', 'triggerRun2_B', 'buttonIgnored', 'buttonIgnored_B', 'buttonIgnored2', 'buttonIgnored2_B', 'buttonIgnored3', 'buttonIgnored3_B', 'buttonIgnored4', 'buttonIgnored4_B', 'buttonVRReorient', 'buttonVRReorient_B', 'analogStickDeadZone', 'analogStickDeadZone_B', 'dpad', 'dpad_B', 'unassignedButtonsRun', 'unassignedButtonsRun_B', 'startButtonActivatesDefaultWidget', 'startButtonActivatesDefaultWidget_B', 'uiOnly', 'uiOnly_B', 'ignoreCompletely', 'ignoreCompletely_B', 'autoRecalibrateAnalogStick', 'autoRecalibrateAnalogStick_B', 'analogStickLR', 'analogStickLR_B', 'analogStickUD', 'analogStickUD_B', 'enableSecondary', ]: val = get_device_value(self._input, skey) if val != -1: self._settings[skey] = val back_button: Optional[ba.Widget] if self._is_secondary: back_button = ba.buttonwidget(parent=self._root_widget, position=(self._width - 180, self._height - 65), autoselect=True, size=(160, 60), label=ba.Lstr(resource='doneText'), scale=0.9, on_activate_call=self._save) ba.containerwidget(edit=self._root_widget, start_button=back_button, on_cancel_call=back_button.activate) cancel_button = None else: cancel_button = ba.buttonwidget( parent=self._root_widget, position=(51, self._height - 65), autoselect=True, size=(160, 60), label=ba.Lstr(resource='cancelText'), scale=0.9, on_activate_call=self._cancel) ba.containerwidget(edit=self._root_widget, cancel_button=cancel_button) save_button: Optional[ba.Widget] if not self._is_secondary: save_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - (165 if self._is_secondary else 195), self._height - 65), size=((160 if self._is_secondary else 180), 60), autoselect=True, label=ba.Lstr(resource='doneText') if self._is_secondary else ba.Lstr(resource='saveText'), scale=0.9, on_activate_call=self._save) ba.containerwidget(edit=self._root_widget, start_button=save_button) else: save_button = None if not self._is_secondary: v = self._height - 59 ba.textwidget(parent=self._root_widget, position=(0, v + 5), size=(self._width, 25), text=ba.Lstr(resource=self._r + '.titleText'), color=ba.app.ui.title_color, maxwidth=310, h_align='center', v_align='center') v -= 48 ba.textwidget(parent=self._root_widget, position=(0, v + 3), size=(self._width, 25), text=self._name, color=ba.app.ui.infotextcolor, maxwidth=self._width * 0.9, h_align='center', v_align='center') v -= self._spacing * 1 ba.textwidget(parent=self._root_widget, position=(50, v + 10), size=(self._width - 100, 30), text=ba.Lstr(resource=self._r + '.appliesToAllText'), maxwidth=330, scale=0.65, color=(0.5, 0.6, 0.5, 1.0), h_align='center', v_align='center') v -= 70 self._enable_check_box = None else: v = self._height - 49 ba.textwidget(parent=self._root_widget, position=(0, v + 5), size=(self._width, 25), text=ba.Lstr(resource=self._r + '.secondaryText'), color=ba.app.ui.title_color, maxwidth=300, h_align='center', v_align='center') v -= self._spacing * 1 ba.textwidget(parent=self._root_widget, position=(50, v + 10), size=(self._width - 100, 30), text=ba.Lstr(resource=self._r + '.secondHalfText'), maxwidth=300, scale=0.65, color=(0.6, 0.8, 0.6, 1.0), h_align='center') self._enable_check_box = ba.checkboxwidget( parent=self._root_widget, position=(self._width * 0.5 - 80, v - 73), value=self.get_enable_secondary_value(), autoselect=True, on_value_change_call=self._enable_check_box_changed, size=(200, 30), text=ba.Lstr(resource=self._r + '.secondaryEnableText'), scale=1.2) v = self._height - 205 h_offs = 160 dist = 70 d_color = (0.4, 0.4, 0.8) sclx = 1.2 scly = 0.98 dpm = ba.Lstr(resource=self._r + '.pressAnyButtonOrDpadText') dpm2 = ba.Lstr(resource=self._r + '.ifNothingHappensTryAnalogText') self._capture_button(pos=(h_offs, v + scly * dist), color=d_color, button='buttonUp' + self._ext, texture=ba.gettexture('upButton'), scale=1.0, message=dpm, message2=dpm2) self._capture_button(pos=(h_offs - sclx * dist, v), color=d_color, button='buttonLeft' + self._ext, texture=ba.gettexture('leftButton'), scale=1.0, message=dpm, message2=dpm2) self._capture_button(pos=(h_offs + sclx * dist, v), color=d_color, button='buttonRight' + self._ext, texture=ba.gettexture('rightButton'), scale=1.0, message=dpm, message2=dpm2) self._capture_button(pos=(h_offs, v - scly * dist), color=d_color, button='buttonDown' + self._ext, texture=ba.gettexture('downButton'), scale=1.0, message=dpm, message2=dpm2) dpm3 = ba.Lstr(resource=self._r + '.ifNothingHappensTryDpadText') self._capture_button(pos=(h_offs + 130, v - 125), color=(0.4, 0.4, 0.6), button='analogStickLR' + self._ext, maxwidth=140, texture=ba.gettexture('analogStick'), scale=1.2, message=ba.Lstr(resource=self._r + '.pressLeftRightText'), message2=dpm3) self._capture_button(pos=(self._width * 0.5, v), color=(0.4, 0.4, 0.6), button='buttonStart' + self._ext, texture=ba.gettexture('startButton'), scale=0.7) h_offs = self._width - 160 self._capture_button(pos=(h_offs, v + scly * dist), color=(0.6, 0.4, 0.8), button='buttonPickUp' + self._ext, texture=ba.gettexture('buttonPickUp'), scale=1.0) self._capture_button(pos=(h_offs - sclx * dist, v), color=(0.7, 0.5, 0.1), button='buttonPunch' + self._ext, texture=ba.gettexture('buttonPunch'), scale=1.0) self._capture_button(pos=(h_offs + sclx * dist, v), color=(0.5, 0.2, 0.1), button='buttonBomb' + self._ext, texture=ba.gettexture('buttonBomb'), scale=1.0) self._capture_button(pos=(h_offs, v - scly * dist), color=(0.2, 0.5, 0.2), button='buttonJump' + self._ext, texture=ba.gettexture('buttonJump'), scale=1.0) self._advanced_button = ba.buttonwidget( parent=self._root_widget, autoselect=True, label=ba.Lstr(resource=self._r + '.advancedText'), text_scale=0.9, color=(0.45, 0.4, 0.5), textcolor=(0.65, 0.6, 0.7), position=(self._width - 300, 30), size=(130, 40), on_activate_call=self._do_advanced) try: if cancel_button is not None and save_button is not None: ba.widget(edit=cancel_button, right_widget=save_button) ba.widget(edit=save_button, left_widget=cancel_button) except Exception: ba.print_exception('Error wiring up gamepad config window.')
def _update_controls(self) -> None: if self._show_fullscreen: ba.checkboxwidget(edit=self._fullscreen_checkbox, value=ba.app.config.resolve('Fullscreen'))
def __init__(self, transition: str = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-locals app = ba.app # If they provided an origin-widget, scale up from that. scale_origin: Optional[Tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None uiscale = ba.app.ui.uiscale self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (390.0 if uiscale is ba.UIScale.SMALL else 450.0 if uiscale is ba.UIScale.MEDIUM else 520.0) top_extra = 10 if uiscale is ba.UIScale.SMALL else 0 super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + top_extra), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=(2.06 if uiscale is ba.UIScale.SMALL else 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0))) self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - 115.0 self._sub_width = self._scroll_width * 0.95 self._sub_height = 724.0 if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: ba.containerwidget(edit=self._root_widget, on_cancel_call=self._do_back) self._back_button = None else: self._back_button = ba.buttonwidget( parent=self._root_widget, position=(53 + x_inset, self._height - 60), size=(140, 60), scale=0.8, autoselect=True, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._do_back) ba.containerwidget(edit=self._root_widget, cancel_button=self._back_button) self._title_text = ba.textwidget(parent=self._root_widget, position=(0, self._height - 52), size=(self._width, 25), text=ba.Lstr(resource='pluginsText'), color=app.ui.title_color, h_align='center', v_align='top') if self._back_button is not None: ba.buttonwidget(edit=self._back_button, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) self._scrollwidget = ba.scrollwidget(parent=self._root_widget, position=(50 + x_inset, 50), simple_culling_v=20.0, highlight=False, size=(self._scroll_width, self._scroll_height), selection_loops_to_parent=True) ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) self._subcontainer = ba.columnwidget(parent=self._scrollwidget, selection_loops_to_parent=True) if ba.app.metascan is None: ba.screenmessage('Still scanning plugins; please try again.', color=(1, 0, 0)) ba.playsound(ba.getsound('error')) pluglist = ba.app.potential_plugins plugstates: Dict[str, Dict] = ba.app.config.setdefault('Plugins', {}) assert isinstance(plugstates, dict) for i, availplug in enumerate(pluglist): active = availplug.class_path in ba.app.active_plugins plugstate = plugstates.setdefault(availplug.class_path, {}) checked = plugstate.get('enabled', False) assert isinstance(checked, bool) check = ba.checkboxwidget( parent=self._subcontainer, text=availplug.display_name, value=checked, maxwidth=self._scroll_width - 100, size=(self._scroll_width - 40, 50), on_value_change_call=ba.Call(self._check_value_changed, availplug), textcolor=((0.8, 0.3, 0.3) if not availplug.available else (0, 1, 0) if active else (0.6, 0.6, 0.6))) # Make sure we scroll all the way to the end when using # keyboard/button nav. ba.widget(edit=check, show_buffer_top=40, show_buffer_bottom=40) # Keep last from looping to back button when down is pressed. if i == len(pluglist) - 1: ba.widget(edit=check, down_widget=check) ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget) self._restore_state()
def __init__(self, parent_window: gpsui.GamepadSettingsWindow): # pylint: disable=too-many-statements self._parent_window = parent_window app = ba.app self._r = parent_window.get_r() uiscale = ba.app.ui.uiscale self._width = 900 if uiscale is ba.UIScale.SMALL else 700 self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = 402 if uiscale is ba.UIScale.SMALL else 512 self._textwidgets: dict[str, ba.Widget] = {} super().__init__(root_widget=ba.containerwidget( transition='in_scale', size=(self._width, self._height), scale=1.06 * (1.85 if uiscale is ba.UIScale.SMALL else 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0), scale_origin_stack_offset=(parent_window.get_advanced_button(). get_screen_space_center()))) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height - (40 if uiscale is ba.UIScale.SMALL else 34)), size=(0, 0), text=ba.Lstr(resource=self._r + '.advancedTitleText'), maxwidth=320, color=ba.app.ui.title_color, h_align='center', v_align='center') back_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(self._width - (176 + x_inset), self._height - (60 if uiscale is ba.UIScale.SMALL else 55)), size=(120, 48), text_scale=0.8, label=ba.Lstr(resource='doneText'), on_activate_call=self._done) ba.containerwidget(edit=self._root_widget, start_button=btn, on_cancel_call=btn.activate) self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - 110 self._sub_width = self._scroll_width - 20 self._sub_height = (940 if self._parent_window.get_is_secondary() else 1040) if app.vr_mode: self._sub_height += 50 self._scrollwidget = ba.scrollwidget( parent=self._root_widget, position=((self._width - self._scroll_width) * 0.5, self._height - 65 - self._scroll_height), size=(self._scroll_width, self._scroll_height), claims_left_right=True, claims_tab=True, selection_loops_to_parent=True) self._subcontainer = ba.containerwidget(parent=self._scrollwidget, size=(self._sub_width, self._sub_height), background=False, claims_left_right=True, claims_tab=True, selection_loops_to_parent=True) ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget) h = 30 v = self._sub_height - 10 h2 = h + 12 # don't allow secondary joysticks to handle unassigned buttons if not self._parent_window.get_is_secondary(): v -= 40 cb1 = ba.checkboxwidget( parent=self._subcontainer, position=(h + 70, v), size=(500, 30), text=ba.Lstr(resource=self._r + '.unassignedButtonsRunText'), textcolor=(0.8, 0.8, 0.8), maxwidth=330, scale=1.0, on_value_change_call=self._parent_window. set_unassigned_buttons_run_value, autoselect=True, value=self._parent_window.get_unassigned_buttons_run_value()) ba.widget(edit=cb1, up_widget=back_button) v -= 60 capb = self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.runButton1Text'), control='buttonRun1' + self._parent_window.get_ext()) if self._parent_window.get_is_secondary(): for widget in capb: ba.widget(edit=widget, up_widget=back_button) v -= 42 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.runButton2Text'), control='buttonRun2' + self._parent_window.get_ext()) ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v - 24), size=(0, 0), text=ba.Lstr(resource=self._r + '.runTriggerDescriptionText'), color=(0.7, 1, 0.7, 0.6), maxwidth=self._sub_width * 0.8, scale=0.7, h_align='center', v_align='center') v -= 85 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.runTrigger1Text'), control='triggerRun1' + self._parent_window.get_ext(), message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText')) v -= 42 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.runTrigger2Text'), control='triggerRun2' + self._parent_window.get_ext(), message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText')) # in vr mode, allow assigning a reset-view button if app.vr_mode: v -= 50 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.vrReorientButtonText'), control='buttonVRReorient' + self._parent_window.get_ext()) v -= 60 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.extraStartButtonText'), control='buttonStart2' + self._parent_window.get_ext()) v -= 60 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.ignoredButton1Text'), control='buttonIgnored' + self._parent_window.get_ext()) v -= 42 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.ignoredButton2Text'), control='buttonIgnored2' + self._parent_window.get_ext()) v -= 42 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.ignoredButton3Text'), control='buttonIgnored3' + self._parent_window.get_ext()) v -= 42 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.ignoredButton4Text'), control='buttonIgnored4' + self._parent_window.get_ext()) ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v - 14), size=(0, 0), text=ba.Lstr(resource=self._r + '.ignoredButtonDescriptionText'), color=(0.7, 1, 0.7, 0.6), scale=0.8, maxwidth=self._sub_width * 0.8, h_align='center', v_align='center') v -= 80 ba.checkboxwidget(parent=self._subcontainer, autoselect=True, position=(h + 50, v), size=(400, 30), text=ba.Lstr(resource=self._r + '.startButtonActivatesDefaultText'), textcolor=(0.8, 0.8, 0.8), maxwidth=450, scale=0.9, on_value_change_call=self._parent_window. set_start_button_activates_default_widget_value, value=self._parent_window. get_start_button_activates_default_widget_value()) ba.textwidget( parent=self._subcontainer, position=(self._sub_width * 0.5, v - 12), size=(0, 0), text=ba.Lstr(resource=self._r + '.startButtonActivatesDefaultDescriptionText'), color=(0.7, 1, 0.7, 0.6), maxwidth=self._sub_width * 0.8, scale=0.7, h_align='center', v_align='center') v -= 80 ba.checkboxwidget( parent=self._subcontainer, autoselect=True, position=(h + 50, v), size=(400, 30), text=ba.Lstr(resource=self._r + '.uiOnlyText'), textcolor=(0.8, 0.8, 0.8), maxwidth=450, scale=0.9, on_value_change_call=self._parent_window.set_ui_only_value, value=self._parent_window.get_ui_only_value()) ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v - 12), size=(0, 0), text=ba.Lstr(resource=self._r + '.uiOnlyDescriptionText'), color=(0.7, 1, 0.7, 0.6), maxwidth=self._sub_width * 0.8, scale=0.7, h_align='center', v_align='center') v -= 80 ba.checkboxwidget( parent=self._subcontainer, autoselect=True, position=(h + 50, v), size=(400, 30), text=ba.Lstr(resource=self._r + '.ignoreCompletelyText'), textcolor=(0.8, 0.8, 0.8), maxwidth=450, scale=0.9, on_value_change_call=self._parent_window. set_ignore_completely_value, value=self._parent_window.get_ignore_completely_value()) ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v - 12), size=(0, 0), text=ba.Lstr(resource=self._r + '.ignoreCompletelyDescriptionText'), color=(0.7, 1, 0.7, 0.6), maxwidth=self._sub_width * 0.8, scale=0.7, h_align='center', v_align='center') v -= 80 cb1 = ba.checkboxwidget( parent=self._subcontainer, autoselect=True, position=(h + 50, v), size=(400, 30), text=ba.Lstr(resource=self._r + '.autoRecalibrateText'), textcolor=(0.8, 0.8, 0.8), maxwidth=450, scale=0.9, on_value_change_call=self._parent_window. set_auto_recalibrate_analog_stick_value, value=self._parent_window.get_auto_recalibrate_analog_stick_value( )) ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v - 12), size=(0, 0), text=ba.Lstr(resource=self._r + '.autoRecalibrateDescriptionText'), color=(0.7, 1, 0.7, 0.6), maxwidth=self._sub_width * 0.8, scale=0.7, h_align='center', v_align='center') v -= 80 buttons = self._config_value_editor( ba.Lstr(resource=self._r + '.analogStickDeadZoneText'), control=('analogStickDeadZone' + self._parent_window.get_ext()), position=(h + 40, v), min_val=0, max_val=10.0, increment=0.1, x_offset=100) ba.widget(edit=buttons[0], left_widget=cb1, up_widget=cb1) ba.widget(edit=cb1, right_widget=buttons[0], down_widget=buttons[0]) ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v - 12), size=(0, 0), text=ba.Lstr(resource=self._r + '.analogStickDeadZoneDescriptionText'), color=(0.7, 1, 0.7, 0.6), maxwidth=self._sub_width * 0.8, scale=0.7, h_align='center', v_align='center') v -= 100 # child joysticks cant have child joysticks.. that's just # crazy talk if not self._parent_window.get_is_secondary(): ba.buttonwidget( parent=self._subcontainer, autoselect=True, label=ba.Lstr(resource=self._r + '.twoInOneSetupText'), position=(40, v), size=(self._sub_width - 80, 50), on_activate_call=self._parent_window.show_secondary_editor, up_widget=buttons[0]) # set a bigger bottom show-buffer for the widgets we just made # so we can see the text below them when navigating with # a gamepad for child in self._subcontainer.get_children(): ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=30)
def __init__(self, transition: str = 'in_right', origin_widget: ba.Widget = None): # FIXME: should tidy up here. # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=cyclic-import from bastd.ui import popup as popup_ui self._have_selected_child = False scale_origin: Optional[Tuple[float, float]] # If they provided an origin-widget, scale up from that. if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None self._r = 'configControllersWindow' app = ba.app is_fire_tv = _ba.is_running_on_fire_tv() spacing = 50.0 button_width = 350.0 width = 460.0 height = 135.0 space_height = spacing * 0.3 # FIXME: should create vis settings in platform for these, # not hard code them here.. show_gamepads = False platform = app.platform subplatform = app.subplatform non_vr_windows = (platform == 'windows' and (subplatform != 'oculus' or not app.vr_mode)) if platform in ('linux', 'android', 'mac') or non_vr_windows: show_gamepads = True height += spacing show_touch = False if _ba.have_touchscreen_input(): show_touch = True height += spacing show_space_1 = False if show_gamepads or show_touch: show_space_1 = True height += space_height show_keyboard = False if _ba.getinputdevice('Keyboard', '#1', doraise=False) is not None: show_keyboard = True height += spacing * 2 show_keyboard_p2 = False if app.vr_mode else show_keyboard if show_keyboard_p2: height += spacing show_space_2 = False if show_keyboard: show_space_2 = True height += space_height if bool(True): show_remote = True height += spacing else: show_remote = False show_ps3 = False if platform == 'mac': show_ps3 = True height += spacing show360 = False if platform == 'mac' or is_fire_tv: show360 = True height += spacing show_mac_wiimote = False if platform == 'mac': show_mac_wiimote = True height += spacing # on non-oculus-vr windows, show an option to disable xinput show_xinput_toggle = False if platform == 'windows' and (subplatform != 'oculus' or not app.vr_mode): show_xinput_toggle = True # on mac builds, show an option to switch between generic and # made-for-iOS/Mac systems # (we can run into problems where devices register as one of each # type otherwise).. show_mac_controller_subsystem = False if platform == 'mac': show_mac_controller_subsystem = True if show_mac_controller_subsystem: height += spacing if show_xinput_toggle: height += spacing super().__init__(root_widget=ba.containerwidget( size=(width, height), transition=transition, scale_origin_stack_offset=scale_origin, scale=(1.7 if show_keyboard else 2.2 ) if ba.app.small_ui else 1.5 if ba.app.med_ui else 1.0)) self._back_button = btn = ba.buttonwidget( parent=self._root_widget, position=(35, height - 60), size=(140, 65), scale=0.8, text_scale=1.2, autoselect=True, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=btn) # need these vars to exist even if the buttons don't self._gamepads_button: Optional[ba.Widget] = None self._touch_button: Optional[ba.Widget] = None self._keyboard_button: Optional[ba.Widget] = None self._keyboard_2_button: Optional[ba.Widget] = None self._idevices_button: Optional[ba.Widget] = None self._ps3_button: Optional[ba.Widget] = None self._xbox_360_button: Optional[ba.Widget] = None self._wiimotes_button: Optional[ba.Widget] = None ba.textwidget(parent=self._root_widget, position=(0, height - 49), size=(width, 25), text=ba.Lstr(resource=self._r + '.titleText'), color=ba.app.title_color, h_align='center', v_align='top') ba.buttonwidget(edit=btn, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) v = height - 75 v -= spacing if show_touch: self._touch_button = btn = ba.buttonwidget( parent=self._root_widget, position=((width - button_width) / 2, v), size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureTouchText'), on_activate_call=self._do_touchscreen) if ba.app.toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) if not self._have_selected_child: ba.containerwidget(edit=self._root_widget, selected_child=self._touch_button) ba.widget(edit=self._back_button, down_widget=self._touch_button) self._have_selected_child = True v -= spacing if show_gamepads: self._gamepads_button = btn = ba.buttonwidget( parent=self._root_widget, position=((width - button_width) / 2 - 7, v), size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureControllersText'), on_activate_call=self._do_gamepads) if ba.app.toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) if not self._have_selected_child: ba.containerwidget(edit=self._root_widget, selected_child=self._gamepads_button) ba.widget(edit=self._back_button, down_widget=self._gamepads_button) self._have_selected_child = True v -= spacing else: self._gamepads_button = None if show_space_1: v -= space_height if show_keyboard: self._keyboard_button = btn = ba.buttonwidget( parent=self._root_widget, position=((width - button_width) / 2 + 5, v), size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureKeyboardText'), on_activate_call=self._config_keyboard) if ba.app.toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) if not self._have_selected_child: ba.containerwidget(edit=self._root_widget, selected_child=self._keyboard_button) ba.widget(edit=self._back_button, down_widget=self._keyboard_button) self._have_selected_child = True v -= spacing if show_keyboard_p2: self._keyboard_2_button = ba.buttonwidget( parent=self._root_widget, position=((width - button_width) / 2 - 3, v), size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureKeyboard2Text'), on_activate_call=self._config_keyboard2) v -= spacing if show_space_2: v -= space_height if show_remote: self._idevices_button = btn = ba.buttonwidget( parent=self._root_widget, position=((width - button_width) / 2 - 5, v), size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureMobileText'), on_activate_call=self._do_mobile_devices) if ba.app.toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) if not self._have_selected_child: ba.containerwidget(edit=self._root_widget, selected_child=self._idevices_button) ba.widget(edit=self._back_button, down_widget=self._idevices_button) self._have_selected_child = True v -= spacing if show_ps3: self._ps3_button = btn = ba.buttonwidget( parent=self._root_widget, position=((width - button_width) / 2 + 5, v), size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.ps3Text'), on_activate_call=self._do_ps3_controllers) if ba.app.toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) v -= spacing if show360: self._xbox_360_button = btn = ba.buttonwidget( parent=self._root_widget, position=((width - button_width) / 2 - 1, v), size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.xbox360Text'), on_activate_call=self._do_360_controllers) if ba.app.toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) v -= spacing if show_mac_wiimote: self._wiimotes_button = btn = ba.buttonwidget( parent=self._root_widget, position=((width - button_width) / 2 + 5, v), size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.wiimotesText'), on_activate_call=self._do_wiimotes) if ba.app.toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) v -= spacing if show_xinput_toggle: def do_toggle(value: bool) -> None: ba.screenmessage( ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'), color=(1, 1, 0)) ba.playsound(ba.getsound('gunCocking')) _ba.set_low_level_config_value('enablexinput', not value) ba.checkboxwidget( parent=self._root_widget, position=(100, v + 3), size=(120, 30), value=(not _ba.get_low_level_config_value('enablexinput', 1)), maxwidth=200, on_value_change_call=do_toggle, text=ba.Lstr(resource='disableXInputText'), autoselect=True) ba.textwidget( parent=self._root_widget, position=(width * 0.5, v - 5), size=(0, 0), text=ba.Lstr(resource='disableXInputDescriptionText'), scale=0.5, h_align='center', v_align='center', color=ba.app.infotextcolor, maxwidth=width * 0.8) v -= spacing if show_mac_controller_subsystem: popup_ui.PopupMenu( parent=self._root_widget, position=(260, v - 10), width=160, button_size=(150, 50), scale=1.5, choices=['Classic', 'MFi', 'Both'], choices_display=[ ba.Lstr(resource='macControllerSubsystemClassicText'), ba.Lstr(resource='macControllerSubsystemMFiText'), ba.Lstr(resource='macControllerSubsystemBothText') ], current_choice=ba.app.config.resolve( 'Mac Controller Subsystem'), on_value_change_call=self._set_mac_controller_subsystem) ba.textwidget( parent=self._root_widget, position=(245, v + 13), size=(0, 0), text=ba.Lstr(resource='macControllerSubsystemTitleText'), scale=1.0, h_align='right', v_align='center', color=ba.app.infotextcolor, maxwidth=180) ba.textwidget( parent=self._root_widget, position=(width * 0.5, v - 20), size=(0, 0), text=ba.Lstr(resource='macControllerSubsystemDescriptionText'), scale=0.5, h_align='center', v_align='center', color=ba.app.infotextcolor, maxwidth=width * 0.8) v -= spacing self._restore_state()
def _rebuild(self) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from bastd.ui.config import ConfigCheckBox from ba.modutils import show_user_scripts available_languages = ba.app.lang.available_languages # Don't rebuild if the menu is open or if our language and # language-list hasn't changed. # NOTE - although we now support widgets updating their own # translations, we still change the label formatting on the language # menu based on the language so still need this. ...however we could # make this more limited to it only rebuilds that one menu instead # of everything. if self._menu_open or (self._prev_lang == _ba.app.config.get( 'Lang', None) and self._prev_lang_list == available_languages): return self._prev_lang = _ba.app.config.get('Lang', None) self._prev_lang_list = available_languages # Clear out our sub-container. children = self._subcontainer.get_children() for child in children: child.delete() v = self._sub_height - 35 v -= self._spacing * 1.2 # Update our existing back button and title. if self._back_button is not None: ba.buttonwidget(edit=self._back_button, label=ba.Lstr(resource='backText')) ba.buttonwidget(edit=self._back_button, label=ba.charstr(ba.SpecialChar.BACK)) ba.textwidget(edit=self._title_text, text=ba.Lstr(resource=self._r + '.titleText')) this_button_width = 410 self._promo_code_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 14), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.enterPromoCodeText'), text_scale=1.0, on_activate_call=self._on_promo_code_press) if self._back_button is not None: ba.widget(edit=self._promo_code_button, up_widget=self._back_button, left_widget=self._back_button) v -= self._extra_button_spacing * 0.8 ba.textwidget(parent=self._subcontainer, position=(200, v + 10), size=(0, 0), text=ba.Lstr(resource=self._r + '.languageText'), maxwidth=150, scale=0.95, color=ba.app.ui.title_color, h_align='right', v_align='center') languages = _ba.app.lang.available_languages cur_lang = _ba.app.config.get('Lang', None) if cur_lang is None: cur_lang = 'Auto' # We have a special dict of language names in that language # so we don't have to go digging through each full language. try: import json with open('ba_data/data/langdata.json', encoding='utf-8') as infile: lang_names_translated = (json.loads( infile.read())['lang_names_translated']) except Exception: ba.print_exception('Error reading lang data.') lang_names_translated = {} langs_translated = {} for lang in languages: langs_translated[lang] = lang_names_translated.get(lang, lang) langs_full = {} for lang in languages: lang_translated = ba.Lstr(translate=('languages', lang)).evaluate() if langs_translated[lang] == lang_translated: langs_full[lang] = lang_translated else: langs_full[lang] = (langs_translated[lang] + ' (' + lang_translated + ')') self._language_popup = popup_ui.PopupMenu( parent=self._subcontainer, position=(210, v - 19), width=150, opening_call=ba.WeakCall(self._on_menu_open), closing_call=ba.WeakCall(self._on_menu_close), autoselect=False, on_value_change_call=ba.WeakCall(self._on_menu_choice), choices=['Auto'] + languages, button_size=(250, 60), choices_display=([ ba.Lstr(value=(ba.Lstr(resource='autoText').evaluate() + ' (' + ba.Lstr(translate=('languages', ba.app.lang.default_language )).evaluate() + ')')) ] + [ba.Lstr(value=langs_full[l]) for l in languages]), current_choice=cur_lang) v -= self._spacing * 1.8 ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v + 10), size=(0, 0), text=ba.Lstr(resource=self._r + '.helpTranslateText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))]), maxwidth=self._sub_width * 0.9, max_height=55, flatness=1.0, scale=0.65, color=(0.4, 0.9, 0.4, 0.8), h_align='center', v_align='center') v -= self._spacing * 1.9 this_button_width = 410 self._translation_editor_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 24), size=(this_button_width, 60), label=ba.Lstr(resource=self._r + '.translationEditorButtonText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) ]), autoselect=True, on_activate_call=ba.Call( ba.open_url, 'https://legacy.ballistica.net/translate')) self._lang_status_text = ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v - 40), size=(0, 0), text='', flatness=1.0, scale=0.63, h_align='center', v_align='center', maxwidth=400.0) self._update_lang_status() v -= 40 lang_inform = _ba.get_v1_account_misc_val('langInform', False) self._language_inform_checkbox = cbw = ba.checkboxwidget( parent=self._subcontainer, position=(50, v - 50), size=(self._sub_width - 100, 30), autoselect=True, maxwidth=430, textcolor=(0.8, 0.8, 0.8), value=lang_inform, text=ba.Lstr(resource=self._r + '.translationInformMe'), on_value_change_call=ba.WeakCall( self._on_lang_inform_value_change)) ba.widget(edit=self._translation_editor_button, down_widget=cbw, up_widget=self._language_popup.get_button()) v -= self._spacing * 3.0 self._kick_idle_players_check_box = ConfigCheckBox( parent=self._subcontainer, position=(50, v), size=(self._sub_width - 100, 30), configkey='Kick Idle Players', displayname=ba.Lstr(resource=self._r + '.kickIdlePlayersText'), scale=1.0, maxwidth=430) v -= 42 self._disable_camera_shake_check_box = ConfigCheckBox( parent=self._subcontainer, position=(50, v), size=(self._sub_width - 100, 30), configkey='Disable Camera Shake', displayname=ba.Lstr(resource=self._r + '.disableCameraShakeText'), scale=1.0, maxwidth=430) self._disable_gyro_check_box: Optional[ConfigCheckBox] = None if self._show_disable_gyro: v -= 42 self._disable_gyro_check_box = ConfigCheckBox( parent=self._subcontainer, position=(50, v), size=(self._sub_width - 100, 30), configkey='Disable Camera Gyro', displayname=ba.Lstr(resource=self._r + '.disableCameraGyroscopeMotionText'), scale=1.0, maxwidth=430) self._always_use_internal_keyboard_check_box: Optional[ConfigCheckBox] if self._show_always_use_internal_keyboard: v -= 42 self._always_use_internal_keyboard_check_box = ConfigCheckBox( parent=self._subcontainer, position=(50, v), size=(self._sub_width - 100, 30), configkey='Always Use Internal Keyboard', autoselect=True, displayname=ba.Lstr(resource=self._r + '.alwaysUseInternalKeyboardText'), scale=1.0, maxwidth=430) ba.textwidget( parent=self._subcontainer, position=(90, v - 10), size=(0, 0), text=ba.Lstr(resource=self._r + '.alwaysUseInternalKeyboardDescriptionText'), maxwidth=400, flatness=1.0, scale=0.65, color=(0.4, 0.9, 0.4, 0.8), h_align='left', v_align='center') v -= 20 else: self._always_use_internal_keyboard_check_box = None v -= self._spacing * 2.1 this_button_width = 410 self._show_user_mods_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 10), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.showUserModsText'), text_scale=1.0, on_activate_call=show_user_scripts) if self._show_always_use_internal_keyboard: assert self._always_use_internal_keyboard_check_box is not None ba.widget(edit=self._always_use_internal_keyboard_check_box.widget, down_widget=self._show_user_mods_button) ba.widget( edit=self._show_user_mods_button, up_widget=self._always_use_internal_keyboard_check_box.widget) else: ba.widget(edit=self._show_user_mods_button, up_widget=self._kick_idle_players_check_box.widget) ba.widget(edit=self._kick_idle_players_check_box.widget, down_widget=self._show_user_mods_button) v -= self._spacing * 2.0 self._modding_guide_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 10), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.moddingGuideText'), text_scale=1.0, on_activate_call=ba.Call( ba.open_url, 'http://www.froemling.net/docs/bombsquad-modding-guide')) v -= self._spacing * 2.0 self._plugins_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 10), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource='pluginsText'), text_scale=1.0, on_activate_call=self._on_plugins_button_press) v -= self._spacing * 0.6 self._vr_test_button: Optional[ba.Widget] if self._do_vr_test_button: v -= self._extra_button_spacing self._vr_test_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 14), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.vrTestingText'), text_scale=1.0, on_activate_call=self._on_vr_test_press) else: self._vr_test_button = None self._net_test_button: Optional[ba.Widget] if self._do_net_test_button: v -= self._extra_button_spacing self._net_test_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 14), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.netTestingText'), text_scale=1.0, on_activate_call=self._on_net_test_press) else: self._net_test_button = None v -= 70 self._benchmarks_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 14), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.benchmarksText'), text_scale=1.0, on_activate_call=self._on_benchmark_press) for child in self._subcontainer.get_children(): ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20) if ba.app.ui.use_toolbars: pbtn = _ba.get_special_widget('party_button') ba.widget(edit=self._scrollwidget, right_widget=pbtn) if self._back_button is None: ba.widget(edit=self._scrollwidget, left_widget=_ba.get_special_widget('back_button')) self._restore_state()
def _radio_press(check_string: str, other_check_boxes: List[ba.Widget], val: int) -> None: if val == 1: value_change_call(check_string) for cbx in other_check_boxes: ba.checkboxwidget(edit=cbx, value=False)
def _build_gui(self) -> None: from bastd.ui import config as cfgui from bastd.ui import radiogroup # clear anything already there children = self._subcontainer.get_children() for child in children: child.delete() h = 30 v = self._sub_height - 85 clr = (0.8, 0.8, 0.8, 1.0) clr2 = (0.8, 0.8, 0.8) ba.textwidget(parent=self._subcontainer, position=(-10, v + 43), size=(self._sub_width, 25), text=ba.Lstr(resource=self._r + '.swipeInfoText'), flatness=1.0, color=(0, 0.9, 0.1, 0.7), maxwidth=self._sub_width * 0.9, scale=0.55, h_align='center', v_align='center') cur_val = ba.app.config.get('Touch Movement Control Type', 'swipe') ba.textwidget(parent=self._subcontainer, position=(h, v - 2), size=(0, 30), text=ba.Lstr(resource=self._r + '.movementText'), maxwidth=190, color=clr, v_align='center') cb1 = ba.checkboxwidget(parent=self._subcontainer, position=(h + 220, v), size=(170, 30), text=ba.Lstr(resource=self._r + '.joystickText'), maxwidth=100, textcolor=clr2, scale=0.9) cb2 = ba.checkboxwidget(parent=self._subcontainer, position=(h + 357, v), size=(170, 30), text=ba.Lstr(resource=self._r + '.swipeText'), maxwidth=100, textcolor=clr2, value=False, scale=0.9) radiogroup.make_radio_group((cb1, cb2), ('joystick', 'swipe'), cur_val, self._movement_changed) v -= 50 cfgui.ConfigNumberEdit( parent=self._subcontainer, position=(h, v), xoffset=65, configkey='Touch Controls Scale Movement', displayname=ba.Lstr(resource=self._r + '.movementControlScaleText'), changesound=False, minval=0.1, maxval=4.0, increment=0.1) v -= 50 cur_val = ba.app.config.get('Touch Action Control Type', 'buttons') ba.textwidget(parent=self._subcontainer, position=(h, v - 2), size=(0, 30), text=ba.Lstr(resource=self._r + '.actionsText'), maxwidth=190, color=clr, v_align='center') cb1 = ba.checkboxwidget(parent=self._subcontainer, position=(h + 220, v), size=(170, 30), text=ba.Lstr(resource=self._r + '.buttonsText'), maxwidth=100, textcolor=clr2, scale=0.9) cb2 = ba.checkboxwidget(parent=self._subcontainer, position=(h + 357, v), size=(170, 30), text=ba.Lstr(resource=self._r + '.swipeText'), maxwidth=100, textcolor=clr2, scale=0.9) radiogroup.make_radio_group((cb1, cb2), ('buttons', 'swipe'), cur_val, self._actions_changed) v -= 50 cfgui.ConfigNumberEdit(parent=self._subcontainer, position=(h, v), xoffset=65, configkey='Touch Controls Scale Actions', displayname=ba.Lstr(resource=self._r + '.actionControlScaleText'), changesound=False, minval=0.1, maxval=4.0, increment=0.1) v -= 50 cfgui.ConfigCheckBox(parent=self._subcontainer, position=(h, v), size=(400, 30), maxwidth=400, configkey='Touch Controls Swipe Hidden', displayname=ba.Lstr(resource=self._r + '.swipeControlsHiddenText')) v -= 65 ba.buttonwidget(parent=self._subcontainer, position=(self._sub_width * 0.5 - 70, v), size=(170, 60), label=ba.Lstr(resource=self._r + '.resetText'), scale=0.75, on_activate_call=self._reset) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, 38), size=(0, 0), h_align='center', text=ba.Lstr(resource=self._r + '.dragControlsText'), maxwidth=self._width * 0.8, scale=0.65, color=(1, 1, 1, 0.4))
def _update_internet_tab(self) -> None: global searchText, textModified def updatepartylist(): self._internet_join_last_refresh_time = now self._first_public_party_list_rebuild_time = ba.time( ba.TimeType.REAL) + 1 app = ba.app _ba.add_transaction( { 'type': 'PUBLIC_PARTY_QUERY', 'proto': app.protocol_version, 'lang': app.language }, callback=ba.WeakCall(self._on_public_party_query_result)) _ba.run_transactions() def checkBox(val): global checkBoxBool checkBoxBool = bool(val) updatepartylist() if not searchText: widgetInstalled = True c_width = self._scroll_width c_height = self._scroll_height - 20 v = c_height - 30 searchText = txt = ba.textwidget( parent=self._tab_container, position=(c_width * 0.5 + 250, v + 105), color=ba.app.ui.title_color, scale=1.3, size=(150, 30), maxwidth=145, h_align='left', v_align='center', click_activate=True, selectable=True, autoselect=True, on_activate_call=lambda: self._set_internet_tab( 'host', playsound=True), editable=True, text='') ba.textwidget(parent=self._tab_container, position=(c_width * 0.5 + 125, v + 122), color=ba.app.ui.title_color, scale=1.1, size=(0, 0), h_align='left', v_align='center', on_activate_call=lambda: self._set_internet_tab( 'host', playsound=True), text='Search:') ba.checkboxwidget(parent=self._tab_container, text="Case-Sensitive", position=(c_width * 0.5 + 125, v + 135), color=ba.app.ui.title_color, textcolor=ba.app.ui.title_color, size=(50, 50), scale=1, value=False, on_value_change_call=checkBox) # pylint: disable=too-many-statements # Special case: if a party-queue window is up, don't do any of this # (keeps things smoother). if ba.app.ui.have_party_queue_window: return # If we've got a party-name text widget, keep its value plugged # into our public host name. text = self._internet_host_name_text if text: name = cast(str, ba.textwidget(query=self._internet_host_name_text)) _ba.set_public_party_name(name) # Show/hide the lock icon depending on if we've got pro. icon = self._internet_lock_icon if icon: if self._is_internet_locked(): ba.imagewidget(edit=icon, opacity=0.5) else: ba.imagewidget(edit=icon, opacity=0.0) if self._internet_tab == 'join': now = ba.time(ba.TimeType.REAL) if (now - self._internet_join_last_refresh_time > 0.001 * _ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)): updatepartylist() search_text = cast(str, ba.textwidget(query=searchText)) if search_text != '': textModified = True x = [] if not checkBoxBool: search_text = search_text.lower() for i in self._public_parties: if not search_text in self._public_parties[i][ 'name'].lower(): x.append(i) else: for i in self._public_parties: if not search_text in self._public_parties[i]['name']: x.append(i) for i in x: del self._public_parties[i] self._first_public_party_list_rebuild_time = ba.time( ba.TimeType.REAL) + 1 self._rebuild_public_party_list() else: if textModified: updatepartylist() textModified = False # Go through our existing public party entries firing off pings # for any that have timed out. for party in list(self._public_parties.values()): if (party['next_ping_time'] <= now and ba.app.ping_thread_count < 15): # Make sure to fully catch up and not to multi-ping if # we're way behind somehow. while party['next_ping_time'] <= now: # Crank the interval up for high-latency parties to # save us some work. mult = 1 if party['ping'] is not None: mult = (10 if party['ping'] > 300 else 5 if party['ping'] > 150 else 2) party[ 'next_ping_time'] += party['ping_interval'] * mult class PingThread(threading.Thread): """Thread for sending out pings.""" def __init__(self, address: str, port: int, call: Callable[[str, int, Optional[int]], Optional[int]]): super().__init__() self._address = address self._port = port self._call = call def run(self) -> None: # pylint: disable=too-many-branches ba.app.ping_thread_count += 1 try: import socket from ba.internal import get_ip_address_type socket_type = get_ip_address_type( self._address) sock = socket.socket(socket_type, socket.SOCK_DGRAM) sock.connect((self._address, self._port)) accessible = False starttime = time.time() # Send a few pings and wait a second for # a response. sock.settimeout(1) for _i in range(3): sock.send(b'\x0b') result: Optional[bytes] try: # 11: BA_PACKET_SIMPLE_PING result = sock.recv(10) except Exception: result = None if result == b'\x0c': # 12: BA_PACKET_SIMPLE_PONG accessible = True break time.sleep(1) sock.close() ping = int((time.time() - starttime) * 1000.0) ba.pushcall(ba.Call( self._call, self._address, self._port, ping if accessible else None), from_other_thread=True) except ConnectionRefusedError: # Fine, server; sorry we pinged you. Hmph. pass except OSError as exc: import errno # Ignore harmless errors. if exc.errno in { errno.EHOSTUNREACH, errno.ENETUNREACH, }: pass elif exc.errno == 10022: # Windows 'invalid argument' error. pass elif exc.errno == 10051: # Windows 'a socket operation was attempted # to an unreachable network' error. pass elif exc.errno == errno.EADDRNOTAVAIL: if self._port == 0: # This has happened. Ignore. pass elif ba.do_once(): print( f'Got EADDRNOTAVAIL on gather ping' f' for addr {self._address}' f' port {self._port}.') else: ba.print_exception( f'Error on gather ping ' f'(errno={exc.errno})', once=True) except Exception: ba.print_exception('Error on gather ping', once=True) ba.app.ping_thread_count -= 1 PingThread(party['address'], party['port'], ba.WeakCall(self._ping_callback)).start()