bauiv1lib.settings.keyboard
Keyboard settings related UI functionality.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Keyboard settings related UI functionality.""" 4 5from __future__ import annotations 6 7from typing import TYPE_CHECKING, override 8 9from bauiv1lib.popup import PopupMenuWindow 10import bauiv1 as bui 11import bascenev1 as bs 12 13if TYPE_CHECKING: 14 from typing import Any 15 16 from bauiv1lib.popup import PopupWindow 17 18 19class ConfigKeyboardWindow(bui.MainWindow): 20 """Window for configuring keyboards.""" 21 22 def __init__( 23 self, 24 c: bs.InputDevice, 25 transition: str | None = 'in_right', 26 origin_widget: bui.Widget | None = None, 27 ): 28 self._r = 'configKeyboardWindow' 29 self._input = c 30 self._name = self._input.name 31 self._unique_id = self._input.unique_identifier 32 dname_raw = self._name 33 if self._unique_id != '#1': 34 dname_raw += ' ' + self._unique_id.replace('#', 'P') 35 self._displayname = bui.Lstr(translate=('inputDeviceNames', dname_raw)) 36 self._width = 700 37 if self._unique_id != '#1': 38 self._height = 480 39 else: 40 self._height = 375 41 self._spacing = 40 42 assert bui.app.classic is not None 43 uiscale = bui.app.ui_v1.uiscale 44 super().__init__( 45 root_widget=bui.containerwidget( 46 size=(self._width, self._height), 47 scale=( 48 1.4 49 if uiscale is bui.UIScale.SMALL 50 else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 51 ), 52 stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0), 53 transition=transition, 54 ), 55 transition=transition, 56 origin_widget=origin_widget, 57 ) 58 59 self._settings: dict[str, int] = {} 60 self._get_config_mapping() 61 62 self._rebuild_ui() 63 64 @override 65 def get_main_window_state(self) -> bui.MainWindowState: 66 # Support recreating our window for back/refresh purposes. 67 cls = type(self) 68 69 # Pull things from self here; if we do it within the lambda 70 # we'll keep self alive which is bad. 71 inputdevice = self._input 72 73 return bui.BasicMainWindowState( 74 create_call=lambda transition, origin_widget: cls( 75 transition=transition, 76 origin_widget=origin_widget, 77 c=inputdevice, 78 ) 79 ) 80 81 def _get_config_mapping(self, default: bool = False) -> None: 82 for button in [ 83 'buttonJump', 84 'buttonPunch', 85 'buttonBomb', 86 'buttonPickUp', 87 'buttonStart', 88 'buttonStart2', 89 'buttonUp', 90 'buttonDown', 91 'buttonLeft', 92 'buttonRight', 93 ]: 94 assert bui.app.classic is not None 95 self._settings[button] = ( 96 bui.app.classic.get_input_device_mapped_value( 97 self._input, button, default 98 ) 99 ) 100 101 def _rebuild_ui(self, is_reset: bool = False) -> None: 102 assert bui.app.classic is not None 103 104 for widget in self._root_widget.get_children(): 105 widget.delete() 106 107 # b_off = 0 if self._unique_id != '#1' else 9 108 cancel_button = bui.buttonwidget( 109 parent=self._root_widget, 110 autoselect=True, 111 position=(38, self._height - 85), 112 size=(170, 60), 113 label=bui.Lstr(resource='cancelText'), 114 scale=0.9, 115 on_activate_call=self.main_window_back, 116 ) 117 save_button = bui.buttonwidget( 118 parent=self._root_widget, 119 autoselect=True, 120 position=(self._width - 190, self._height - 85), 121 size=(180, 60), 122 label=bui.Lstr(resource='saveText'), 123 scale=0.9, 124 text_scale=0.9, 125 on_activate_call=self._save, 126 ) 127 bui.containerwidget( 128 edit=self._root_widget, 129 cancel_button=cancel_button, 130 start_button=save_button, 131 ) 132 133 v = self._height - 74.0 134 bui.textwidget( 135 parent=self._root_widget, 136 position=(self._width * 0.5, v + 15), 137 size=(0, 0), 138 text=bui.Lstr( 139 resource=f'{self._r}.configuringText', 140 subs=[('${DEVICE}', self._displayname)], 141 ), 142 color=bui.app.ui_v1.title_color, 143 h_align='center', 144 v_align='center', 145 maxwidth=270, 146 scale=0.83, 147 ) 148 v -= 20 149 150 if self._unique_id != '#1': 151 v -= 20 152 v -= self._spacing 153 bui.textwidget( 154 parent=self._root_widget, 155 position=(0, v + 19), 156 size=(self._width, 50), 157 text=bui.Lstr(resource=f'{self._r}.keyboard2NoteText'), 158 scale=0.7, 159 maxwidth=self._width * 0.75, 160 max_height=110, 161 color=bui.app.ui_v1.infotextcolor, 162 h_align='center', 163 v_align='top', 164 ) 165 v -= 40 166 v -= 10 167 v -= self._spacing * 2.2 168 v += 25 169 v -= 42 170 h_offs = 160 171 dist = 70 172 d_color = (0.4, 0.4, 0.8) 173 self._capture_button( 174 pos=(h_offs, v + 0.95 * dist), 175 color=d_color, 176 button='buttonUp', 177 texture=bui.gettexture('upButton'), 178 scale=1.0, 179 ) 180 self._capture_button( 181 pos=(h_offs - 1.2 * dist, v), 182 color=d_color, 183 button='buttonLeft', 184 texture=bui.gettexture('leftButton'), 185 scale=1.0, 186 ) 187 self._capture_button( 188 pos=(h_offs + 1.2 * dist, v), 189 color=d_color, 190 button='buttonRight', 191 texture=bui.gettexture('rightButton'), 192 scale=1.0, 193 ) 194 self._capture_button( 195 pos=(h_offs, v - 0.95 * dist), 196 color=d_color, 197 button='buttonDown', 198 texture=bui.gettexture('downButton'), 199 scale=1.0, 200 ) 201 202 if self._unique_id == '#2': 203 self._capture_button( 204 pos=(self._width * 0.5, v + 0.1 * dist), 205 color=(0.4, 0.4, 0.6), 206 button='buttonStart', 207 texture=bui.gettexture('startButton'), 208 scale=0.8, 209 ) 210 211 h_offs = self._width - 160 212 213 self._capture_button( 214 pos=(h_offs, v + 0.95 * dist), 215 color=(0.6, 0.4, 0.8), 216 button='buttonPickUp', 217 texture=bui.gettexture('buttonPickUp'), 218 scale=1.0, 219 ) 220 self._capture_button( 221 pos=(h_offs - 1.2 * dist, v), 222 color=(0.7, 0.5, 0.1), 223 button='buttonPunch', 224 texture=bui.gettexture('buttonPunch'), 225 scale=1.0, 226 ) 227 self._capture_button( 228 pos=(h_offs + 1.2 * dist, v), 229 color=(0.5, 0.2, 0.1), 230 button='buttonBomb', 231 texture=bui.gettexture('buttonBomb'), 232 scale=1.0, 233 ) 234 self._capture_button( 235 pos=(h_offs, v - 0.95 * dist), 236 color=(0.2, 0.5, 0.2), 237 button='buttonJump', 238 texture=bui.gettexture('buttonJump'), 239 scale=1.0, 240 ) 241 242 self._more_button = bui.buttonwidget( 243 parent=self._root_widget, 244 autoselect=True, 245 label='...', 246 text_scale=0.9, 247 color=(0.45, 0.4, 0.5), 248 textcolor=(0.65, 0.6, 0.7), 249 position=(self._width * 0.5 - 65, 30), 250 size=(130, 40), 251 on_activate_call=self._do_more, 252 ) 253 254 if is_reset: 255 bui.containerwidget( 256 edit=self._root_widget, 257 selected_child=self._more_button, 258 ) 259 260 def _pretty_button_name(self, button_name: str) -> bui.Lstr: 261 button_id = self._settings[button_name] 262 if button_id == -1: 263 return bs.Lstr(resource='configGamepadWindow.unsetText') 264 return self._input.get_button_name(button_id) 265 266 def _capture_button( 267 self, 268 pos: tuple[float, float], 269 color: tuple[float, float, float], 270 texture: bui.Texture, 271 button: str, 272 scale: float = 1.0, 273 ) -> None: 274 # pylint: disable=too-many-positional-arguments 275 base_size = 79 276 btn = bui.buttonwidget( 277 parent=self._root_widget, 278 autoselect=True, 279 position=( 280 pos[0] - base_size * 0.5 * scale, 281 pos[1] - base_size * 0.5 * scale, 282 ), 283 size=(base_size * scale, base_size * scale), 284 texture=texture, 285 label='', 286 color=color, 287 ) 288 289 # Do this deferred so it shows up on top of other buttons. (ew.) 290 def doit() -> None: 291 if not self._root_widget: 292 return 293 uiscale = 0.66 * scale * 2.0 294 maxwidth = 76.0 * scale 295 txt = bui.textwidget( 296 parent=self._root_widget, 297 position=(pos[0] + 0.0 * scale, pos[1] - (57.0 - 18.0) * scale), 298 color=(1, 1, 1, 0.3), 299 size=(0, 0), 300 h_align='center', 301 v_align='top', 302 scale=uiscale, 303 maxwidth=maxwidth, 304 text=self._pretty_button_name(button), 305 ) 306 bui.buttonwidget( 307 edit=btn, 308 autoselect=True, 309 on_activate_call=bui.Call( 310 AwaitKeyboardInputWindow, button, txt, self._settings 311 ), 312 ) 313 314 bui.pushcall(doit) 315 316 def _reset(self) -> None: 317 from bauiv1lib.confirm import ConfirmWindow 318 319 assert bui.app.classic is not None 320 321 # efro note: I think it's ok to reset without a confirm here 322 # because the user can see pretty clearly what changes and can 323 # cancel out of the keyboard settings edit if they want. 324 if bool(False): 325 ConfirmWindow( 326 # TODO: Implement a translation string for this! 327 'Are you sure you want to reset your button mapping?', 328 self._do_reset, 329 width=480, 330 height=95, 331 ) 332 else: 333 self._do_reset() 334 335 def _do_reset(self) -> None: 336 """Resets the input's mapping settings.""" 337 self._settings = {} 338 self._get_config_mapping(default=True) 339 self._rebuild_ui(is_reset=True) 340 bui.getsound('gunCocking').play() 341 342 def _do_more(self) -> None: 343 """Show a burger menu with extra settings.""" 344 # pylint: disable=cyclic-import 345 choices: list[str] = [ 346 'reset', 347 ] 348 choices_display: list[bui.Lstr] = [ 349 bui.Lstr(resource='settingsWindowAdvanced.resetText'), 350 ] 351 352 uiscale = bui.app.ui_v1.uiscale 353 PopupMenuWindow( 354 position=self._more_button.get_screen_space_center(), 355 scale=( 356 2.3 357 if uiscale is bui.UIScale.SMALL 358 else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 359 ), 360 width=150, 361 choices=choices, 362 choices_display=choices_display, 363 current_choice='reset', 364 delegate=self, 365 ) 366 367 def popup_menu_selected_choice( 368 self, popup_window: PopupMenuWindow, choice: str 369 ) -> None: 370 """Called when a choice is selected in the popup.""" 371 del popup_window # unused 372 if choice == 'reset': 373 self._reset() 374 else: 375 print(f'invalid choice: {choice}') 376 377 def popup_menu_closing(self, popup_window: PopupWindow) -> None: 378 """Called when the popup is closing.""" 379 380 def _save(self) -> None: 381 382 # no-op if we're not in control. 383 if not self.main_window_has_control(): 384 return 385 386 assert bui.app.classic is not None 387 bui.getsound('gunCocking').play() 388 389 # There's a chance the device disappeared; handle that 390 # gracefully. 391 if not self._input: 392 return 393 394 dst = bui.app.classic.get_input_device_config( 395 self._input, default=False 396 ) 397 dst2: dict[str, Any] = dst[0][dst[1]] 398 dst2.clear() 399 400 # Store any values that aren't -1. 401 for key, val in list(self._settings.items()): 402 if val != -1: 403 dst2[key] = val 404 405 # Send this config to the master-server so we can generate more 406 # defaults in the future. 407 if bui.app.classic is not None: 408 bui.app.classic.master_server_v1_post( 409 'controllerConfig', 410 { 411 'ua': bui.app.classic.legacy_user_agent_string, 412 'name': self._name, 413 'b': bui.app.env.engine_build_number, 414 'config': dst2, 415 'v': 2, 416 }, 417 ) 418 bui.app.config.apply_and_commit() 419 420 self.main_window_back() 421 422 423class AwaitKeyboardInputWindow(bui.Window): 424 """Window for capturing a keypress.""" 425 426 def __init__(self, button: str, ui: bui.Widget, settings: dict): 427 self._capture_button = button 428 self._capture_key_ui = ui 429 self._settings = settings 430 431 width = 400 432 height = 150 433 assert bui.app.classic is not None 434 uiscale = bui.app.ui_v1.uiscale 435 super().__init__( 436 root_widget=bui.containerwidget( 437 size=(width, height), 438 transition='in_right', 439 scale=( 440 2.0 441 if uiscale is bui.UIScale.SMALL 442 else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 443 ), 444 ) 445 ) 446 bui.textwidget( 447 parent=self._root_widget, 448 position=(0, height - 60), 449 size=(width, 25), 450 text=bui.Lstr(resource='pressAnyKeyText'), 451 h_align='center', 452 v_align='top', 453 ) 454 455 self._counter = 5 456 self._count_down_text = bui.textwidget( 457 parent=self._root_widget, 458 h_align='center', 459 position=(0, height - 110), 460 size=(width, 25), 461 color=(1, 1, 1, 0.3), 462 text=str(self._counter), 463 ) 464 self._decrement_timer: bui.AppTimer | None = bui.AppTimer( 465 1.0, self._decrement, repeat=True 466 ) 467 bs.capture_keyboard_input(bui.WeakCall(self._button_callback)) 468 469 def __del__(self) -> None: 470 bs.release_keyboard_input() 471 472 def _die(self) -> None: 473 # This strong-refs us; killing it allows us to die now. 474 self._decrement_timer = None 475 if self._root_widget: 476 bui.containerwidget(edit=self._root_widget, transition='out_left') 477 478 def _button_callback(self, event: dict[str, Any]) -> None: 479 self._settings[self._capture_button] = event['button'] 480 if event['type'] == 'BUTTONDOWN': 481 bname = event['input_device'].get_button_name(event['button']) 482 bui.textwidget(edit=self._capture_key_ui, text=bname) 483 bui.getsound('gunCocking').play() 484 self._die() 485 486 def _decrement(self) -> None: 487 self._counter -= 1 488 if self._counter >= 1: 489 bui.textwidget(edit=self._count_down_text, text=str(self._counter)) 490 else: 491 self._die()
class
ConfigKeyboardWindow(bauiv1._uitypes.MainWindow):
20class ConfigKeyboardWindow(bui.MainWindow): 21 """Window for configuring keyboards.""" 22 23 def __init__( 24 self, 25 c: bs.InputDevice, 26 transition: str | None = 'in_right', 27 origin_widget: bui.Widget | None = None, 28 ): 29 self._r = 'configKeyboardWindow' 30 self._input = c 31 self._name = self._input.name 32 self._unique_id = self._input.unique_identifier 33 dname_raw = self._name 34 if self._unique_id != '#1': 35 dname_raw += ' ' + self._unique_id.replace('#', 'P') 36 self._displayname = bui.Lstr(translate=('inputDeviceNames', dname_raw)) 37 self._width = 700 38 if self._unique_id != '#1': 39 self._height = 480 40 else: 41 self._height = 375 42 self._spacing = 40 43 assert bui.app.classic is not None 44 uiscale = bui.app.ui_v1.uiscale 45 super().__init__( 46 root_widget=bui.containerwidget( 47 size=(self._width, self._height), 48 scale=( 49 1.4 50 if uiscale is bui.UIScale.SMALL 51 else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 52 ), 53 stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0), 54 transition=transition, 55 ), 56 transition=transition, 57 origin_widget=origin_widget, 58 ) 59 60 self._settings: dict[str, int] = {} 61 self._get_config_mapping() 62 63 self._rebuild_ui() 64 65 @override 66 def get_main_window_state(self) -> bui.MainWindowState: 67 # Support recreating our window for back/refresh purposes. 68 cls = type(self) 69 70 # Pull things from self here; if we do it within the lambda 71 # we'll keep self alive which is bad. 72 inputdevice = self._input 73 74 return bui.BasicMainWindowState( 75 create_call=lambda transition, origin_widget: cls( 76 transition=transition, 77 origin_widget=origin_widget, 78 c=inputdevice, 79 ) 80 ) 81 82 def _get_config_mapping(self, default: bool = False) -> None: 83 for button in [ 84 'buttonJump', 85 'buttonPunch', 86 'buttonBomb', 87 'buttonPickUp', 88 'buttonStart', 89 'buttonStart2', 90 'buttonUp', 91 'buttonDown', 92 'buttonLeft', 93 'buttonRight', 94 ]: 95 assert bui.app.classic is not None 96 self._settings[button] = ( 97 bui.app.classic.get_input_device_mapped_value( 98 self._input, button, default 99 ) 100 ) 101 102 def _rebuild_ui(self, is_reset: bool = False) -> None: 103 assert bui.app.classic is not None 104 105 for widget in self._root_widget.get_children(): 106 widget.delete() 107 108 # b_off = 0 if self._unique_id != '#1' else 9 109 cancel_button = bui.buttonwidget( 110 parent=self._root_widget, 111 autoselect=True, 112 position=(38, self._height - 85), 113 size=(170, 60), 114 label=bui.Lstr(resource='cancelText'), 115 scale=0.9, 116 on_activate_call=self.main_window_back, 117 ) 118 save_button = bui.buttonwidget( 119 parent=self._root_widget, 120 autoselect=True, 121 position=(self._width - 190, self._height - 85), 122 size=(180, 60), 123 label=bui.Lstr(resource='saveText'), 124 scale=0.9, 125 text_scale=0.9, 126 on_activate_call=self._save, 127 ) 128 bui.containerwidget( 129 edit=self._root_widget, 130 cancel_button=cancel_button, 131 start_button=save_button, 132 ) 133 134 v = self._height - 74.0 135 bui.textwidget( 136 parent=self._root_widget, 137 position=(self._width * 0.5, v + 15), 138 size=(0, 0), 139 text=bui.Lstr( 140 resource=f'{self._r}.configuringText', 141 subs=[('${DEVICE}', self._displayname)], 142 ), 143 color=bui.app.ui_v1.title_color, 144 h_align='center', 145 v_align='center', 146 maxwidth=270, 147 scale=0.83, 148 ) 149 v -= 20 150 151 if self._unique_id != '#1': 152 v -= 20 153 v -= self._spacing 154 bui.textwidget( 155 parent=self._root_widget, 156 position=(0, v + 19), 157 size=(self._width, 50), 158 text=bui.Lstr(resource=f'{self._r}.keyboard2NoteText'), 159 scale=0.7, 160 maxwidth=self._width * 0.75, 161 max_height=110, 162 color=bui.app.ui_v1.infotextcolor, 163 h_align='center', 164 v_align='top', 165 ) 166 v -= 40 167 v -= 10 168 v -= self._spacing * 2.2 169 v += 25 170 v -= 42 171 h_offs = 160 172 dist = 70 173 d_color = (0.4, 0.4, 0.8) 174 self._capture_button( 175 pos=(h_offs, v + 0.95 * dist), 176 color=d_color, 177 button='buttonUp', 178 texture=bui.gettexture('upButton'), 179 scale=1.0, 180 ) 181 self._capture_button( 182 pos=(h_offs - 1.2 * dist, v), 183 color=d_color, 184 button='buttonLeft', 185 texture=bui.gettexture('leftButton'), 186 scale=1.0, 187 ) 188 self._capture_button( 189 pos=(h_offs + 1.2 * dist, v), 190 color=d_color, 191 button='buttonRight', 192 texture=bui.gettexture('rightButton'), 193 scale=1.0, 194 ) 195 self._capture_button( 196 pos=(h_offs, v - 0.95 * dist), 197 color=d_color, 198 button='buttonDown', 199 texture=bui.gettexture('downButton'), 200 scale=1.0, 201 ) 202 203 if self._unique_id == '#2': 204 self._capture_button( 205 pos=(self._width * 0.5, v + 0.1 * dist), 206 color=(0.4, 0.4, 0.6), 207 button='buttonStart', 208 texture=bui.gettexture('startButton'), 209 scale=0.8, 210 ) 211 212 h_offs = self._width - 160 213 214 self._capture_button( 215 pos=(h_offs, v + 0.95 * dist), 216 color=(0.6, 0.4, 0.8), 217 button='buttonPickUp', 218 texture=bui.gettexture('buttonPickUp'), 219 scale=1.0, 220 ) 221 self._capture_button( 222 pos=(h_offs - 1.2 * dist, v), 223 color=(0.7, 0.5, 0.1), 224 button='buttonPunch', 225 texture=bui.gettexture('buttonPunch'), 226 scale=1.0, 227 ) 228 self._capture_button( 229 pos=(h_offs + 1.2 * dist, v), 230 color=(0.5, 0.2, 0.1), 231 button='buttonBomb', 232 texture=bui.gettexture('buttonBomb'), 233 scale=1.0, 234 ) 235 self._capture_button( 236 pos=(h_offs, v - 0.95 * dist), 237 color=(0.2, 0.5, 0.2), 238 button='buttonJump', 239 texture=bui.gettexture('buttonJump'), 240 scale=1.0, 241 ) 242 243 self._more_button = bui.buttonwidget( 244 parent=self._root_widget, 245 autoselect=True, 246 label='...', 247 text_scale=0.9, 248 color=(0.45, 0.4, 0.5), 249 textcolor=(0.65, 0.6, 0.7), 250 position=(self._width * 0.5 - 65, 30), 251 size=(130, 40), 252 on_activate_call=self._do_more, 253 ) 254 255 if is_reset: 256 bui.containerwidget( 257 edit=self._root_widget, 258 selected_child=self._more_button, 259 ) 260 261 def _pretty_button_name(self, button_name: str) -> bui.Lstr: 262 button_id = self._settings[button_name] 263 if button_id == -1: 264 return bs.Lstr(resource='configGamepadWindow.unsetText') 265 return self._input.get_button_name(button_id) 266 267 def _capture_button( 268 self, 269 pos: tuple[float, float], 270 color: tuple[float, float, float], 271 texture: bui.Texture, 272 button: str, 273 scale: float = 1.0, 274 ) -> None: 275 # pylint: disable=too-many-positional-arguments 276 base_size = 79 277 btn = bui.buttonwidget( 278 parent=self._root_widget, 279 autoselect=True, 280 position=( 281 pos[0] - base_size * 0.5 * scale, 282 pos[1] - base_size * 0.5 * scale, 283 ), 284 size=(base_size * scale, base_size * scale), 285 texture=texture, 286 label='', 287 color=color, 288 ) 289 290 # Do this deferred so it shows up on top of other buttons. (ew.) 291 def doit() -> None: 292 if not self._root_widget: 293 return 294 uiscale = 0.66 * scale * 2.0 295 maxwidth = 76.0 * scale 296 txt = bui.textwidget( 297 parent=self._root_widget, 298 position=(pos[0] + 0.0 * scale, pos[1] - (57.0 - 18.0) * scale), 299 color=(1, 1, 1, 0.3), 300 size=(0, 0), 301 h_align='center', 302 v_align='top', 303 scale=uiscale, 304 maxwidth=maxwidth, 305 text=self._pretty_button_name(button), 306 ) 307 bui.buttonwidget( 308 edit=btn, 309 autoselect=True, 310 on_activate_call=bui.Call( 311 AwaitKeyboardInputWindow, button, txt, self._settings 312 ), 313 ) 314 315 bui.pushcall(doit) 316 317 def _reset(self) -> None: 318 from bauiv1lib.confirm import ConfirmWindow 319 320 assert bui.app.classic is not None 321 322 # efro note: I think it's ok to reset without a confirm here 323 # because the user can see pretty clearly what changes and can 324 # cancel out of the keyboard settings edit if they want. 325 if bool(False): 326 ConfirmWindow( 327 # TODO: Implement a translation string for this! 328 'Are you sure you want to reset your button mapping?', 329 self._do_reset, 330 width=480, 331 height=95, 332 ) 333 else: 334 self._do_reset() 335 336 def _do_reset(self) -> None: 337 """Resets the input's mapping settings.""" 338 self._settings = {} 339 self._get_config_mapping(default=True) 340 self._rebuild_ui(is_reset=True) 341 bui.getsound('gunCocking').play() 342 343 def _do_more(self) -> None: 344 """Show a burger menu with extra settings.""" 345 # pylint: disable=cyclic-import 346 choices: list[str] = [ 347 'reset', 348 ] 349 choices_display: list[bui.Lstr] = [ 350 bui.Lstr(resource='settingsWindowAdvanced.resetText'), 351 ] 352 353 uiscale = bui.app.ui_v1.uiscale 354 PopupMenuWindow( 355 position=self._more_button.get_screen_space_center(), 356 scale=( 357 2.3 358 if uiscale is bui.UIScale.SMALL 359 else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 360 ), 361 width=150, 362 choices=choices, 363 choices_display=choices_display, 364 current_choice='reset', 365 delegate=self, 366 ) 367 368 def popup_menu_selected_choice( 369 self, popup_window: PopupMenuWindow, choice: str 370 ) -> None: 371 """Called when a choice is selected in the popup.""" 372 del popup_window # unused 373 if choice == 'reset': 374 self._reset() 375 else: 376 print(f'invalid choice: {choice}') 377 378 def popup_menu_closing(self, popup_window: PopupWindow) -> None: 379 """Called when the popup is closing.""" 380 381 def _save(self) -> None: 382 383 # no-op if we're not in control. 384 if not self.main_window_has_control(): 385 return 386 387 assert bui.app.classic is not None 388 bui.getsound('gunCocking').play() 389 390 # There's a chance the device disappeared; handle that 391 # gracefully. 392 if not self._input: 393 return 394 395 dst = bui.app.classic.get_input_device_config( 396 self._input, default=False 397 ) 398 dst2: dict[str, Any] = dst[0][dst[1]] 399 dst2.clear() 400 401 # Store any values that aren't -1. 402 for key, val in list(self._settings.items()): 403 if val != -1: 404 dst2[key] = val 405 406 # Send this config to the master-server so we can generate more 407 # defaults in the future. 408 if bui.app.classic is not None: 409 bui.app.classic.master_server_v1_post( 410 'controllerConfig', 411 { 412 'ua': bui.app.classic.legacy_user_agent_string, 413 'name': self._name, 414 'b': bui.app.env.engine_build_number, 415 'config': dst2, 416 'v': 2, 417 }, 418 ) 419 bui.app.config.apply_and_commit() 420 421 self.main_window_back()
Window for configuring keyboards.
ConfigKeyboardWindow( c: _bascenev1.InputDevice, transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
23 def __init__( 24 self, 25 c: bs.InputDevice, 26 transition: str | None = 'in_right', 27 origin_widget: bui.Widget | None = None, 28 ): 29 self._r = 'configKeyboardWindow' 30 self._input = c 31 self._name = self._input.name 32 self._unique_id = self._input.unique_identifier 33 dname_raw = self._name 34 if self._unique_id != '#1': 35 dname_raw += ' ' + self._unique_id.replace('#', 'P') 36 self._displayname = bui.Lstr(translate=('inputDeviceNames', dname_raw)) 37 self._width = 700 38 if self._unique_id != '#1': 39 self._height = 480 40 else: 41 self._height = 375 42 self._spacing = 40 43 assert bui.app.classic is not None 44 uiscale = bui.app.ui_v1.uiscale 45 super().__init__( 46 root_widget=bui.containerwidget( 47 size=(self._width, self._height), 48 scale=( 49 1.4 50 if uiscale is bui.UIScale.SMALL 51 else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 52 ), 53 stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0), 54 transition=transition, 55 ), 56 transition=transition, 57 origin_widget=origin_widget, 58 ) 59 60 self._settings: dict[str, int] = {} 61 self._get_config_mapping() 62 63 self._rebuild_ui()
Create a MainWindow given a root widget and transition info.
Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.
65 @override 66 def get_main_window_state(self) -> bui.MainWindowState: 67 # Support recreating our window for back/refresh purposes. 68 cls = type(self) 69 70 # Pull things from self here; if we do it within the lambda 71 # we'll keep self alive which is bad. 72 inputdevice = self._input 73 74 return bui.BasicMainWindowState( 75 create_call=lambda transition, origin_widget: cls( 76 transition=transition, 77 origin_widget=origin_widget, 78 c=inputdevice, 79 ) 80 )
Return a WindowState to recreate this window, if supported.
class
AwaitKeyboardInputWindow(bauiv1._uitypes.Window):
424class AwaitKeyboardInputWindow(bui.Window): 425 """Window for capturing a keypress.""" 426 427 def __init__(self, button: str, ui: bui.Widget, settings: dict): 428 self._capture_button = button 429 self._capture_key_ui = ui 430 self._settings = settings 431 432 width = 400 433 height = 150 434 assert bui.app.classic is not None 435 uiscale = bui.app.ui_v1.uiscale 436 super().__init__( 437 root_widget=bui.containerwidget( 438 size=(width, height), 439 transition='in_right', 440 scale=( 441 2.0 442 if uiscale is bui.UIScale.SMALL 443 else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 444 ), 445 ) 446 ) 447 bui.textwidget( 448 parent=self._root_widget, 449 position=(0, height - 60), 450 size=(width, 25), 451 text=bui.Lstr(resource='pressAnyKeyText'), 452 h_align='center', 453 v_align='top', 454 ) 455 456 self._counter = 5 457 self._count_down_text = bui.textwidget( 458 parent=self._root_widget, 459 h_align='center', 460 position=(0, height - 110), 461 size=(width, 25), 462 color=(1, 1, 1, 0.3), 463 text=str(self._counter), 464 ) 465 self._decrement_timer: bui.AppTimer | None = bui.AppTimer( 466 1.0, self._decrement, repeat=True 467 ) 468 bs.capture_keyboard_input(bui.WeakCall(self._button_callback)) 469 470 def __del__(self) -> None: 471 bs.release_keyboard_input() 472 473 def _die(self) -> None: 474 # This strong-refs us; killing it allows us to die now. 475 self._decrement_timer = None 476 if self._root_widget: 477 bui.containerwidget(edit=self._root_widget, transition='out_left') 478 479 def _button_callback(self, event: dict[str, Any]) -> None: 480 self._settings[self._capture_button] = event['button'] 481 if event['type'] == 'BUTTONDOWN': 482 bname = event['input_device'].get_button_name(event['button']) 483 bui.textwidget(edit=self._capture_key_ui, text=bname) 484 bui.getsound('gunCocking').play() 485 self._die() 486 487 def _decrement(self) -> None: 488 self._counter -= 1 489 if self._counter >= 1: 490 bui.textwidget(edit=self._count_down_text, text=str(self._counter)) 491 else: 492 self._die()
Window for capturing a keypress.
AwaitKeyboardInputWindow(button: str, ui: _bauiv1.Widget, settings: dict)
427 def __init__(self, button: str, ui: bui.Widget, settings: dict): 428 self._capture_button = button 429 self._capture_key_ui = ui 430 self._settings = settings 431 432 width = 400 433 height = 150 434 assert bui.app.classic is not None 435 uiscale = bui.app.ui_v1.uiscale 436 super().__init__( 437 root_widget=bui.containerwidget( 438 size=(width, height), 439 transition='in_right', 440 scale=( 441 2.0 442 if uiscale is bui.UIScale.SMALL 443 else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 444 ), 445 ) 446 ) 447 bui.textwidget( 448 parent=self._root_widget, 449 position=(0, height - 60), 450 size=(width, 25), 451 text=bui.Lstr(resource='pressAnyKeyText'), 452 h_align='center', 453 v_align='top', 454 ) 455 456 self._counter = 5 457 self._count_down_text = bui.textwidget( 458 parent=self._root_widget, 459 h_align='center', 460 position=(0, height - 110), 461 size=(width, 25), 462 color=(1, 1, 1, 0.3), 463 text=str(self._counter), 464 ) 465 self._decrement_timer: bui.AppTimer | None = bui.AppTimer( 466 1.0, self._decrement, repeat=True 467 ) 468 bs.capture_keyboard_input(bui.WeakCall(self._button_callback))