bauiv1lib.watch
Provides UI functionality for watching replays.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides UI functionality for watching replays.""" 4 5from __future__ import annotations 6 7import os 8import logging 9from enum import Enum 10from typing import TYPE_CHECKING, cast, override 11 12import bascenev1 as bs 13import bauiv1 as bui 14 15if TYPE_CHECKING: 16 from typing import Any 17 18 19class WatchWindow(bui.MainWindow): 20 """Window for watching replays.""" 21 22 class TabID(Enum): 23 """Our available tab types.""" 24 25 MY_REPLAYS = 'my_replays' 26 TEST_TAB = 'test_tab' 27 28 def __init__( 29 self, 30 transition: str | None = 'in_right', 31 origin_widget: bui.Widget | None = None, 32 ): 33 # pylint: disable=too-many-locals 34 from bauiv1lib.tabs import TabRow 35 36 bui.set_analytics_screen('Watch Window') 37 self._tab_data: dict[str, Any] = {} 38 self._my_replays_scroll_width: float | None = None 39 self._my_replays_watch_replay_button: bui.Widget | None = None 40 self._scrollwidget: bui.Widget | None = None 41 self._columnwidget: bui.Widget | None = None 42 self._my_replay_selected: str | None = None 43 self._my_replays_rename_window: bui.Widget | None = None 44 self._my_replay_rename_text: bui.Widget | None = None 45 self._r = 'watchWindow' 46 uiscale = bui.app.ui_v1.uiscale 47 self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040 48 x_inset = 200 if uiscale is bui.UIScale.SMALL else 0 49 self._height = ( 50 570 51 if uiscale is bui.UIScale.SMALL 52 else 670 if uiscale is bui.UIScale.MEDIUM else 800 53 ) 54 self._current_tab: WatchWindow.TabID | None = None 55 extra_top = 20 if uiscale is bui.UIScale.SMALL else 0 56 57 super().__init__( 58 root_widget=bui.containerwidget( 59 size=(self._width, self._height + extra_top), 60 toolbar_visibility=( 61 'menu_minimal' 62 if uiscale is bui.UIScale.SMALL 63 else 'menu_full' 64 ), 65 scale=( 66 1.32 67 if uiscale is bui.UIScale.SMALL 68 else 0.85 if uiscale is bui.UIScale.MEDIUM else 0.65 69 ), 70 stack_offset=( 71 (0, 30) 72 if uiscale is bui.UIScale.SMALL 73 else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) 74 ), 75 ), 76 transition=transition, 77 origin_widget=origin_widget, 78 ) 79 80 if uiscale is bui.UIScale.SMALL: 81 bui.containerwidget( 82 edit=self._root_widget, on_cancel_call=self.main_window_back 83 ) 84 self._back_button = None 85 else: 86 self._back_button = btn = bui.buttonwidget( 87 parent=self._root_widget, 88 autoselect=True, 89 position=(70 + x_inset, self._height - 74), 90 size=(140, 60), 91 scale=1.1, 92 label=bui.Lstr(resource='backText'), 93 button_type='back', 94 on_activate_call=self.main_window_back, 95 ) 96 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 97 bui.buttonwidget( 98 edit=btn, 99 button_type='backSmall', 100 size=(60, 60), 101 label=bui.charstr(bui.SpecialChar.BACK), 102 ) 103 104 bui.textwidget( 105 parent=self._root_widget, 106 position=( 107 self._width * 0.5, 108 self._height - (65 if uiscale is bui.UIScale.SMALL else 38), 109 ), 110 size=(0, 0), 111 color=bui.app.ui_v1.title_color, 112 scale=0.7 if uiscale is bui.UIScale.SMALL else 1.5, 113 h_align='center', 114 v_align='center', 115 text=( 116 '' 117 if uiscale is bui.UIScale.SMALL 118 else bui.Lstr(resource=f'{self._r}.titleText') 119 ), 120 maxwidth=400, 121 ) 122 123 tabdefs = [ 124 ( 125 self.TabID.MY_REPLAYS, 126 bui.Lstr(resource=f'{self._r}.myReplaysText'), 127 ), 128 # (self.TabID.TEST_TAB, bui.Lstr(value='Testing')), 129 ] 130 131 scroll_buffer_h = 130 + 2 * x_inset 132 tab_buffer_h = 750 + 2 * x_inset 133 134 self._tab_row = TabRow( 135 self._root_widget, 136 tabdefs, 137 pos=(tab_buffer_h * 0.5, self._height - 130), 138 size=(self._width - tab_buffer_h, 50), 139 on_select_call=self._set_tab, 140 ) 141 142 first_tab = self._tab_row.tabs[tabdefs[0][0]] 143 last_tab = self._tab_row.tabs[tabdefs[-1][0]] 144 bui.widget( 145 edit=last_tab.button, 146 right_widget=bui.get_special_widget('squad_button'), 147 ) 148 if uiscale is bui.UIScale.SMALL: 149 bbtn = bui.get_special_widget('back_button') 150 bui.widget(edit=first_tab.button, up_widget=bbtn, left_widget=bbtn) 151 152 self._scroll_width = self._width - scroll_buffer_h 153 self._scroll_height = self._height - 180 154 155 # Not actually using a scroll widget anymore; just an image. 156 scroll_left = (self._width - self._scroll_width) * 0.5 157 scroll_bottom = self._height - self._scroll_height - 79 - 48 158 buffer_h = 10 159 buffer_v = 4 160 bui.imagewidget( 161 parent=self._root_widget, 162 position=(scroll_left - buffer_h, scroll_bottom - buffer_v), 163 size=( 164 self._scroll_width + 2 * buffer_h, 165 self._scroll_height + 2 * buffer_v, 166 ), 167 texture=bui.gettexture('scrollWidget'), 168 mesh_transparent=bui.getmesh('softEdgeOutside'), 169 ) 170 self._tab_container: bui.Widget | None = None 171 172 self._restore_state() 173 174 @override 175 def get_main_window_state(self) -> bui.MainWindowState: 176 # Support recreating our window for back/refresh purposes. 177 cls = type(self) 178 return bui.BasicMainWindowState( 179 create_call=lambda transition, origin_widget: cls( 180 transition=transition, origin_widget=origin_widget 181 ) 182 ) 183 184 @override 185 def on_main_window_close(self) -> None: 186 self._save_state() 187 188 def _set_tab(self, tab_id: TabID) -> None: 189 # pylint: disable=too-many-locals 190 191 if self._current_tab == tab_id: 192 return 193 self._current_tab = tab_id 194 195 # Preserve our current tab between runs. 196 cfg = bui.app.config 197 cfg['Watch Tab'] = tab_id.value 198 cfg.commit() 199 200 # Update tab colors based on which is selected. 201 # tabs.update_tab_button_colors(self._tab_buttons, tab) 202 self._tab_row.update_appearance(tab_id) 203 204 if self._tab_container: 205 self._tab_container.delete() 206 scroll_left = (self._width - self._scroll_width) * 0.5 207 scroll_bottom = self._height - self._scroll_height - 79 - 48 208 209 # A place where tabs can store data to get cleared when 210 # switching to a different tab 211 self._tab_data = {} 212 213 assert bui.app.classic is not None 214 uiscale = bui.app.ui_v1.uiscale 215 if tab_id is self.TabID.MY_REPLAYS: 216 c_width = self._scroll_width 217 c_height = self._scroll_height - 20 218 sub_scroll_height = c_height - 63 219 self._my_replays_scroll_width = sub_scroll_width = ( 220 680 if uiscale is bui.UIScale.SMALL else 640 221 ) 222 223 self._tab_container = cnt = bui.containerwidget( 224 parent=self._root_widget, 225 position=( 226 scroll_left, 227 scroll_bottom + (self._scroll_height - c_height) * 0.5, 228 ), 229 size=(c_width, c_height), 230 background=False, 231 selection_loops_to_parent=True, 232 ) 233 234 v = c_height - 30 235 bui.textwidget( 236 parent=cnt, 237 position=(c_width * 0.5, v), 238 color=(0.6, 1.0, 0.6), 239 scale=0.7, 240 size=(0, 0), 241 maxwidth=c_width * 0.9, 242 h_align='center', 243 v_align='center', 244 text=bui.Lstr( 245 resource='replayRenameWarningText', 246 subs=[ 247 ( 248 '${REPLAY}', 249 bui.Lstr(resource='replayNameDefaultText'), 250 ) 251 ], 252 ), 253 ) 254 255 b_width = 140 if uiscale is bui.UIScale.SMALL else 178 256 b_height = ( 257 107 258 if uiscale is bui.UIScale.SMALL 259 else 142 if uiscale is bui.UIScale.MEDIUM else 190 260 ) 261 b_space_extra = ( 262 0 263 if uiscale is bui.UIScale.SMALL 264 else -2 if uiscale is bui.UIScale.MEDIUM else -5 265 ) 266 267 b_color = (0.6, 0.53, 0.63) 268 b_textcolor = (0.75, 0.7, 0.8) 269 btnv = ( 270 c_height 271 - ( 272 48 273 if uiscale is bui.UIScale.SMALL 274 else 45 if uiscale is bui.UIScale.MEDIUM else 40 275 ) 276 - b_height 277 ) 278 btnh = 40 if uiscale is bui.UIScale.SMALL else 40 279 smlh = 190 if uiscale is bui.UIScale.SMALL else 225 280 tscl = 1.0 if uiscale is bui.UIScale.SMALL else 1.2 281 self._my_replays_watch_replay_button = btn1 = bui.buttonwidget( 282 parent=cnt, 283 size=(b_width, b_height), 284 position=(btnh, btnv), 285 button_type='square', 286 color=b_color, 287 textcolor=b_textcolor, 288 on_activate_call=self._on_my_replay_play_press, 289 text_scale=tscl, 290 label=bui.Lstr(resource=f'{self._r}.watchReplayButtonText'), 291 autoselect=True, 292 ) 293 bui.widget(edit=btn1, up_widget=self._tab_row.tabs[tab_id].button) 294 assert bui.app.classic is not None 295 if uiscale is bui.UIScale.SMALL: 296 bui.widget( 297 edit=btn1, 298 left_widget=bui.get_special_widget('back_button'), 299 ) 300 btnv -= b_height + b_space_extra 301 bui.buttonwidget( 302 parent=cnt, 303 size=(b_width, b_height), 304 position=(btnh, btnv), 305 button_type='square', 306 color=b_color, 307 textcolor=b_textcolor, 308 on_activate_call=self._on_my_replay_rename_press, 309 text_scale=tscl, 310 label=bui.Lstr(resource=f'{self._r}.renameReplayButtonText'), 311 autoselect=True, 312 ) 313 btnv -= b_height + b_space_extra 314 bui.buttonwidget( 315 parent=cnt, 316 size=(b_width, b_height), 317 position=(btnh, btnv), 318 button_type='square', 319 color=b_color, 320 textcolor=b_textcolor, 321 on_activate_call=self._on_my_replay_delete_press, 322 text_scale=tscl, 323 label=bui.Lstr(resource=f'{self._r}.deleteReplayButtonText'), 324 autoselect=True, 325 ) 326 327 v -= sub_scroll_height + 23 328 self._scrollwidget = scrlw = bui.scrollwidget( 329 parent=cnt, 330 position=(smlh, v), 331 size=(sub_scroll_width, sub_scroll_height), 332 ) 333 bui.containerwidget(edit=cnt, selected_child=scrlw) 334 self._columnwidget = bui.columnwidget( 335 parent=scrlw, left_border=10, border=2, margin=0 336 ) 337 338 bui.widget( 339 edit=scrlw, 340 autoselect=True, 341 left_widget=btn1, 342 up_widget=self._tab_row.tabs[tab_id].button, 343 ) 344 bui.widget( 345 edit=self._tab_row.tabs[tab_id].button, down_widget=scrlw 346 ) 347 348 self._my_replay_selected = None 349 self._refresh_my_replays() 350 351 def _no_replay_selected_error(self) -> None: 352 bui.screenmessage( 353 bui.Lstr(resource=f'{self._r}.noReplaySelectedErrorText'), 354 color=(1, 0, 0), 355 ) 356 bui.getsound('error').play() 357 358 def _on_my_replay_play_press(self) -> None: 359 if self._my_replay_selected is None: 360 self._no_replay_selected_error() 361 return 362 bui.increment_analytics_count('Replay watch') 363 364 def do_it() -> None: 365 try: 366 # Reset to normal speed. 367 bs.set_replay_speed_exponent(0) 368 bui.fade_screen(True) 369 assert self._my_replay_selected is not None 370 bs.new_replay_session( 371 bui.get_replays_dir() + '/' + self._my_replay_selected 372 ) 373 except Exception: 374 logging.exception('Error running replay session.') 375 376 # Drop back into a fresh main menu session 377 # in case we half-launched or something. 378 from bascenev1lib import mainmenu 379 380 bs.new_host_session(mainmenu.MainMenuSession) 381 382 bui.fade_screen(False, endcall=bui.Call(bui.pushcall, do_it)) 383 bui.containerwidget(edit=self._root_widget, transition='out_left') 384 385 def _on_my_replay_rename_press(self) -> None: 386 if self._my_replay_selected is None: 387 self._no_replay_selected_error() 388 return 389 c_width = 600 390 c_height = 250 391 assert bui.app.classic is not None 392 uiscale = bui.app.ui_v1.uiscale 393 self._my_replays_rename_window = cnt = bui.containerwidget( 394 scale=( 395 1.8 396 if uiscale is bui.UIScale.SMALL 397 else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.0 398 ), 399 size=(c_width, c_height), 400 transition='in_scale', 401 ) 402 dname = self._get_replay_display_name(self._my_replay_selected) 403 bui.textwidget( 404 parent=cnt, 405 size=(0, 0), 406 h_align='center', 407 v_align='center', 408 text=bui.Lstr( 409 resource=f'{self._r}.renameReplayText', 410 subs=[('${REPLAY}', dname)], 411 ), 412 maxwidth=c_width * 0.8, 413 position=(c_width * 0.5, c_height - 60), 414 ) 415 self._my_replay_rename_text = txt = bui.textwidget( 416 parent=cnt, 417 size=(c_width * 0.8, 40), 418 h_align='left', 419 v_align='center', 420 text=dname, 421 editable=True, 422 description=bui.Lstr(resource=f'{self._r}.replayNameText'), 423 position=(c_width * 0.1, c_height - 140), 424 autoselect=True, 425 maxwidth=c_width * 0.7, 426 max_chars=200, 427 ) 428 cbtn = bui.buttonwidget( 429 parent=cnt, 430 label=bui.Lstr(resource='cancelText'), 431 on_activate_call=bui.Call( 432 lambda c: bui.containerwidget(edit=c, transition='out_scale'), 433 cnt, 434 ), 435 size=(180, 60), 436 position=(30, 30), 437 autoselect=True, 438 ) 439 okb = bui.buttonwidget( 440 parent=cnt, 441 label=bui.Lstr(resource=f'{self._r}.renameText'), 442 size=(180, 60), 443 position=(c_width - 230, 30), 444 on_activate_call=bui.Call( 445 self._rename_my_replay, self._my_replay_selected 446 ), 447 autoselect=True, 448 ) 449 bui.widget(edit=cbtn, right_widget=okb) 450 bui.widget(edit=okb, left_widget=cbtn) 451 bui.textwidget(edit=txt, on_return_press_call=okb.activate) 452 bui.containerwidget(edit=cnt, cancel_button=cbtn, start_button=okb) 453 454 def _rename_my_replay(self, replay: str) -> None: 455 new_name = None 456 try: 457 if not self._my_replay_rename_text: 458 return 459 new_name_raw = cast( 460 str, bui.textwidget(query=self._my_replay_rename_text) 461 ) 462 new_name = new_name_raw + '.brp' 463 464 # Ignore attempts to change it to what it already is 465 # (or what it looks like to the user). 466 if ( 467 replay != new_name 468 and self._get_replay_display_name(replay) != new_name_raw 469 ): 470 old_name_full = (bui.get_replays_dir() + '/' + replay).encode( 471 'utf-8' 472 ) 473 new_name_full = (bui.get_replays_dir() + '/' + new_name).encode( 474 'utf-8' 475 ) 476 # False alarm; bui.textwidget can return non-None val. 477 # pylint: disable=unsupported-membership-test 478 if os.path.exists(new_name_full): 479 bui.getsound('error').play() 480 bui.screenmessage( 481 bui.Lstr( 482 resource=self._r 483 + '.replayRenameErrorAlreadyExistsText' 484 ), 485 color=(1, 0, 0), 486 ) 487 elif any(char in new_name_raw for char in ['/', '\\', ':']): 488 bui.getsound('error').play() 489 bui.screenmessage( 490 bui.Lstr( 491 resource=f'{self._r}.replayRenameErrorInvalidName' 492 ), 493 color=(1, 0, 0), 494 ) 495 else: 496 bui.increment_analytics_count('Replay rename') 497 os.rename(old_name_full, new_name_full) 498 self._refresh_my_replays() 499 bui.getsound('gunCocking').play() 500 except Exception: 501 logging.exception( 502 "Error renaming replay '%s' to '%s'.", replay, new_name 503 ) 504 bui.getsound('error').play() 505 bui.screenmessage( 506 bui.Lstr(resource=f'{self._r}.replayRenameErrorText'), 507 color=(1, 0, 0), 508 ) 509 510 bui.containerwidget( 511 edit=self._my_replays_rename_window, transition='out_scale' 512 ) 513 514 def _on_my_replay_delete_press(self) -> None: 515 from bauiv1lib import confirm 516 517 if self._my_replay_selected is None: 518 self._no_replay_selected_error() 519 return 520 confirm.ConfirmWindow( 521 bui.Lstr( 522 resource=f'{self._r}.deleteConfirmText', 523 subs=[ 524 ( 525 '${REPLAY}', 526 self._get_replay_display_name(self._my_replay_selected), 527 ) 528 ], 529 ), 530 bui.Call(self._delete_replay, self._my_replay_selected), 531 450, 532 150, 533 ) 534 535 def _get_replay_display_name(self, replay: str) -> str: 536 if replay.endswith('.brp'): 537 replay = replay[:-4] 538 if replay == '__lastReplay': 539 return bui.Lstr(resource='replayNameDefaultText').evaluate() 540 return replay 541 542 def _delete_replay(self, replay: str) -> None: 543 try: 544 bui.increment_analytics_count('Replay delete') 545 os.remove((bui.get_replays_dir() + '/' + replay).encode('utf-8')) 546 self._refresh_my_replays() 547 bui.getsound('shieldDown').play() 548 if replay == self._my_replay_selected: 549 self._my_replay_selected = None 550 except Exception: 551 logging.exception("Error deleting replay '%s'.", replay) 552 bui.getsound('error').play() 553 bui.screenmessage( 554 bui.Lstr(resource=f'{self._r}.replayDeleteErrorText'), 555 color=(1, 0, 0), 556 ) 557 558 def _on_my_replay_select(self, replay: str) -> None: 559 self._my_replay_selected = replay 560 561 def _refresh_my_replays(self) -> None: 562 assert self._columnwidget is not None 563 for child in self._columnwidget.get_children(): 564 child.delete() 565 t_scale = 1.6 566 try: 567 names = os.listdir(bui.get_replays_dir()) 568 569 # Ignore random other files in there. 570 names = [n for n in names if n.endswith('.brp')] 571 names.sort(key=lambda x: x.lower()) 572 except Exception: 573 logging.exception('Error listing replays dir.') 574 names = [] 575 576 assert self._my_replays_scroll_width is not None 577 assert self._my_replays_watch_replay_button is not None 578 for i, name in enumerate(names): 579 txt = bui.textwidget( 580 parent=self._columnwidget, 581 size=(self._my_replays_scroll_width / t_scale, 30), 582 selectable=True, 583 color=( 584 (1.0, 1, 0.4) if name == '__lastReplay.brp' else (1, 1, 1) 585 ), 586 always_highlight=True, 587 on_select_call=bui.Call(self._on_my_replay_select, name), 588 on_activate_call=self._my_replays_watch_replay_button.activate, 589 text=self._get_replay_display_name(name), 590 h_align='left', 591 v_align='center', 592 corner_scale=t_scale, 593 maxwidth=(self._my_replays_scroll_width / t_scale) * 0.93, 594 ) 595 if i == 0: 596 bui.widget( 597 edit=txt, 598 up_widget=self._tab_row.tabs[self.TabID.MY_REPLAYS].button, 599 ) 600 self._my_replay_selected = name 601 602 def _save_state(self) -> None: 603 try: 604 sel = self._root_widget.get_selected_child() 605 selected_tab_ids = [ 606 tab_id 607 for tab_id, tab in self._tab_row.tabs.items() 608 if sel == tab.button 609 ] 610 if sel == self._back_button: 611 sel_name = 'Back' 612 elif selected_tab_ids: 613 assert len(selected_tab_ids) == 1 614 sel_name = f'Tab:{selected_tab_ids[0].value}' 615 elif sel == self._tab_container: 616 sel_name = 'TabContainer' 617 else: 618 raise ValueError(f'unrecognized selection {sel}') 619 assert bui.app.classic is not None 620 bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name} 621 except Exception: 622 logging.exception('Error saving state for %s.', self) 623 624 def _restore_state(self) -> None: 625 try: 626 sel: bui.Widget | None 627 assert bui.app.classic is not None 628 sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get( 629 'sel_name' 630 ) 631 assert isinstance(sel_name, (str, type(None))) 632 try: 633 current_tab = self.TabID(bui.app.config.get('Watch Tab')) 634 except ValueError: 635 current_tab = self.TabID.MY_REPLAYS 636 self._set_tab(current_tab) 637 638 if sel_name == 'Back': 639 sel = self._back_button 640 elif sel_name == 'TabContainer': 641 sel = self._tab_container 642 elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): 643 try: 644 sel_tab_id = self.TabID(sel_name.split(':')[-1]) 645 except ValueError: 646 sel_tab_id = self.TabID.MY_REPLAYS 647 sel = self._tab_row.tabs[sel_tab_id].button 648 else: 649 if self._tab_container is not None: 650 sel = self._tab_container 651 else: 652 sel = self._tab_row.tabs[current_tab].button 653 bui.containerwidget(edit=self._root_widget, selected_child=sel) 654 except Exception: 655 logging.exception('Error restoring state for %s.', self)
class
WatchWindow(bauiv1._uitypes.MainWindow):
20class WatchWindow(bui.MainWindow): 21 """Window for watching replays.""" 22 23 class TabID(Enum): 24 """Our available tab types.""" 25 26 MY_REPLAYS = 'my_replays' 27 TEST_TAB = 'test_tab' 28 29 def __init__( 30 self, 31 transition: str | None = 'in_right', 32 origin_widget: bui.Widget | None = None, 33 ): 34 # pylint: disable=too-many-locals 35 from bauiv1lib.tabs import TabRow 36 37 bui.set_analytics_screen('Watch Window') 38 self._tab_data: dict[str, Any] = {} 39 self._my_replays_scroll_width: float | None = None 40 self._my_replays_watch_replay_button: bui.Widget | None = None 41 self._scrollwidget: bui.Widget | None = None 42 self._columnwidget: bui.Widget | None = None 43 self._my_replay_selected: str | None = None 44 self._my_replays_rename_window: bui.Widget | None = None 45 self._my_replay_rename_text: bui.Widget | None = None 46 self._r = 'watchWindow' 47 uiscale = bui.app.ui_v1.uiscale 48 self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040 49 x_inset = 200 if uiscale is bui.UIScale.SMALL else 0 50 self._height = ( 51 570 52 if uiscale is bui.UIScale.SMALL 53 else 670 if uiscale is bui.UIScale.MEDIUM else 800 54 ) 55 self._current_tab: WatchWindow.TabID | None = None 56 extra_top = 20 if uiscale is bui.UIScale.SMALL else 0 57 58 super().__init__( 59 root_widget=bui.containerwidget( 60 size=(self._width, self._height + extra_top), 61 toolbar_visibility=( 62 'menu_minimal' 63 if uiscale is bui.UIScale.SMALL 64 else 'menu_full' 65 ), 66 scale=( 67 1.32 68 if uiscale is bui.UIScale.SMALL 69 else 0.85 if uiscale is bui.UIScale.MEDIUM else 0.65 70 ), 71 stack_offset=( 72 (0, 30) 73 if uiscale is bui.UIScale.SMALL 74 else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) 75 ), 76 ), 77 transition=transition, 78 origin_widget=origin_widget, 79 ) 80 81 if uiscale is bui.UIScale.SMALL: 82 bui.containerwidget( 83 edit=self._root_widget, on_cancel_call=self.main_window_back 84 ) 85 self._back_button = None 86 else: 87 self._back_button = btn = bui.buttonwidget( 88 parent=self._root_widget, 89 autoselect=True, 90 position=(70 + x_inset, self._height - 74), 91 size=(140, 60), 92 scale=1.1, 93 label=bui.Lstr(resource='backText'), 94 button_type='back', 95 on_activate_call=self.main_window_back, 96 ) 97 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 98 bui.buttonwidget( 99 edit=btn, 100 button_type='backSmall', 101 size=(60, 60), 102 label=bui.charstr(bui.SpecialChar.BACK), 103 ) 104 105 bui.textwidget( 106 parent=self._root_widget, 107 position=( 108 self._width * 0.5, 109 self._height - (65 if uiscale is bui.UIScale.SMALL else 38), 110 ), 111 size=(0, 0), 112 color=bui.app.ui_v1.title_color, 113 scale=0.7 if uiscale is bui.UIScale.SMALL else 1.5, 114 h_align='center', 115 v_align='center', 116 text=( 117 '' 118 if uiscale is bui.UIScale.SMALL 119 else bui.Lstr(resource=f'{self._r}.titleText') 120 ), 121 maxwidth=400, 122 ) 123 124 tabdefs = [ 125 ( 126 self.TabID.MY_REPLAYS, 127 bui.Lstr(resource=f'{self._r}.myReplaysText'), 128 ), 129 # (self.TabID.TEST_TAB, bui.Lstr(value='Testing')), 130 ] 131 132 scroll_buffer_h = 130 + 2 * x_inset 133 tab_buffer_h = 750 + 2 * x_inset 134 135 self._tab_row = TabRow( 136 self._root_widget, 137 tabdefs, 138 pos=(tab_buffer_h * 0.5, self._height - 130), 139 size=(self._width - tab_buffer_h, 50), 140 on_select_call=self._set_tab, 141 ) 142 143 first_tab = self._tab_row.tabs[tabdefs[0][0]] 144 last_tab = self._tab_row.tabs[tabdefs[-1][0]] 145 bui.widget( 146 edit=last_tab.button, 147 right_widget=bui.get_special_widget('squad_button'), 148 ) 149 if uiscale is bui.UIScale.SMALL: 150 bbtn = bui.get_special_widget('back_button') 151 bui.widget(edit=first_tab.button, up_widget=bbtn, left_widget=bbtn) 152 153 self._scroll_width = self._width - scroll_buffer_h 154 self._scroll_height = self._height - 180 155 156 # Not actually using a scroll widget anymore; just an image. 157 scroll_left = (self._width - self._scroll_width) * 0.5 158 scroll_bottom = self._height - self._scroll_height - 79 - 48 159 buffer_h = 10 160 buffer_v = 4 161 bui.imagewidget( 162 parent=self._root_widget, 163 position=(scroll_left - buffer_h, scroll_bottom - buffer_v), 164 size=( 165 self._scroll_width + 2 * buffer_h, 166 self._scroll_height + 2 * buffer_v, 167 ), 168 texture=bui.gettexture('scrollWidget'), 169 mesh_transparent=bui.getmesh('softEdgeOutside'), 170 ) 171 self._tab_container: bui.Widget | None = None 172 173 self._restore_state() 174 175 @override 176 def get_main_window_state(self) -> bui.MainWindowState: 177 # Support recreating our window for back/refresh purposes. 178 cls = type(self) 179 return bui.BasicMainWindowState( 180 create_call=lambda transition, origin_widget: cls( 181 transition=transition, origin_widget=origin_widget 182 ) 183 ) 184 185 @override 186 def on_main_window_close(self) -> None: 187 self._save_state() 188 189 def _set_tab(self, tab_id: TabID) -> None: 190 # pylint: disable=too-many-locals 191 192 if self._current_tab == tab_id: 193 return 194 self._current_tab = tab_id 195 196 # Preserve our current tab between runs. 197 cfg = bui.app.config 198 cfg['Watch Tab'] = tab_id.value 199 cfg.commit() 200 201 # Update tab colors based on which is selected. 202 # tabs.update_tab_button_colors(self._tab_buttons, tab) 203 self._tab_row.update_appearance(tab_id) 204 205 if self._tab_container: 206 self._tab_container.delete() 207 scroll_left = (self._width - self._scroll_width) * 0.5 208 scroll_bottom = self._height - self._scroll_height - 79 - 48 209 210 # A place where tabs can store data to get cleared when 211 # switching to a different tab 212 self._tab_data = {} 213 214 assert bui.app.classic is not None 215 uiscale = bui.app.ui_v1.uiscale 216 if tab_id is self.TabID.MY_REPLAYS: 217 c_width = self._scroll_width 218 c_height = self._scroll_height - 20 219 sub_scroll_height = c_height - 63 220 self._my_replays_scroll_width = sub_scroll_width = ( 221 680 if uiscale is bui.UIScale.SMALL else 640 222 ) 223 224 self._tab_container = cnt = bui.containerwidget( 225 parent=self._root_widget, 226 position=( 227 scroll_left, 228 scroll_bottom + (self._scroll_height - c_height) * 0.5, 229 ), 230 size=(c_width, c_height), 231 background=False, 232 selection_loops_to_parent=True, 233 ) 234 235 v = c_height - 30 236 bui.textwidget( 237 parent=cnt, 238 position=(c_width * 0.5, v), 239 color=(0.6, 1.0, 0.6), 240 scale=0.7, 241 size=(0, 0), 242 maxwidth=c_width * 0.9, 243 h_align='center', 244 v_align='center', 245 text=bui.Lstr( 246 resource='replayRenameWarningText', 247 subs=[ 248 ( 249 '${REPLAY}', 250 bui.Lstr(resource='replayNameDefaultText'), 251 ) 252 ], 253 ), 254 ) 255 256 b_width = 140 if uiscale is bui.UIScale.SMALL else 178 257 b_height = ( 258 107 259 if uiscale is bui.UIScale.SMALL 260 else 142 if uiscale is bui.UIScale.MEDIUM else 190 261 ) 262 b_space_extra = ( 263 0 264 if uiscale is bui.UIScale.SMALL 265 else -2 if uiscale is bui.UIScale.MEDIUM else -5 266 ) 267 268 b_color = (0.6, 0.53, 0.63) 269 b_textcolor = (0.75, 0.7, 0.8) 270 btnv = ( 271 c_height 272 - ( 273 48 274 if uiscale is bui.UIScale.SMALL 275 else 45 if uiscale is bui.UIScale.MEDIUM else 40 276 ) 277 - b_height 278 ) 279 btnh = 40 if uiscale is bui.UIScale.SMALL else 40 280 smlh = 190 if uiscale is bui.UIScale.SMALL else 225 281 tscl = 1.0 if uiscale is bui.UIScale.SMALL else 1.2 282 self._my_replays_watch_replay_button = btn1 = bui.buttonwidget( 283 parent=cnt, 284 size=(b_width, b_height), 285 position=(btnh, btnv), 286 button_type='square', 287 color=b_color, 288 textcolor=b_textcolor, 289 on_activate_call=self._on_my_replay_play_press, 290 text_scale=tscl, 291 label=bui.Lstr(resource=f'{self._r}.watchReplayButtonText'), 292 autoselect=True, 293 ) 294 bui.widget(edit=btn1, up_widget=self._tab_row.tabs[tab_id].button) 295 assert bui.app.classic is not None 296 if uiscale is bui.UIScale.SMALL: 297 bui.widget( 298 edit=btn1, 299 left_widget=bui.get_special_widget('back_button'), 300 ) 301 btnv -= b_height + b_space_extra 302 bui.buttonwidget( 303 parent=cnt, 304 size=(b_width, b_height), 305 position=(btnh, btnv), 306 button_type='square', 307 color=b_color, 308 textcolor=b_textcolor, 309 on_activate_call=self._on_my_replay_rename_press, 310 text_scale=tscl, 311 label=bui.Lstr(resource=f'{self._r}.renameReplayButtonText'), 312 autoselect=True, 313 ) 314 btnv -= b_height + b_space_extra 315 bui.buttonwidget( 316 parent=cnt, 317 size=(b_width, b_height), 318 position=(btnh, btnv), 319 button_type='square', 320 color=b_color, 321 textcolor=b_textcolor, 322 on_activate_call=self._on_my_replay_delete_press, 323 text_scale=tscl, 324 label=bui.Lstr(resource=f'{self._r}.deleteReplayButtonText'), 325 autoselect=True, 326 ) 327 328 v -= sub_scroll_height + 23 329 self._scrollwidget = scrlw = bui.scrollwidget( 330 parent=cnt, 331 position=(smlh, v), 332 size=(sub_scroll_width, sub_scroll_height), 333 ) 334 bui.containerwidget(edit=cnt, selected_child=scrlw) 335 self._columnwidget = bui.columnwidget( 336 parent=scrlw, left_border=10, border=2, margin=0 337 ) 338 339 bui.widget( 340 edit=scrlw, 341 autoselect=True, 342 left_widget=btn1, 343 up_widget=self._tab_row.tabs[tab_id].button, 344 ) 345 bui.widget( 346 edit=self._tab_row.tabs[tab_id].button, down_widget=scrlw 347 ) 348 349 self._my_replay_selected = None 350 self._refresh_my_replays() 351 352 def _no_replay_selected_error(self) -> None: 353 bui.screenmessage( 354 bui.Lstr(resource=f'{self._r}.noReplaySelectedErrorText'), 355 color=(1, 0, 0), 356 ) 357 bui.getsound('error').play() 358 359 def _on_my_replay_play_press(self) -> None: 360 if self._my_replay_selected is None: 361 self._no_replay_selected_error() 362 return 363 bui.increment_analytics_count('Replay watch') 364 365 def do_it() -> None: 366 try: 367 # Reset to normal speed. 368 bs.set_replay_speed_exponent(0) 369 bui.fade_screen(True) 370 assert self._my_replay_selected is not None 371 bs.new_replay_session( 372 bui.get_replays_dir() + '/' + self._my_replay_selected 373 ) 374 except Exception: 375 logging.exception('Error running replay session.') 376 377 # Drop back into a fresh main menu session 378 # in case we half-launched or something. 379 from bascenev1lib import mainmenu 380 381 bs.new_host_session(mainmenu.MainMenuSession) 382 383 bui.fade_screen(False, endcall=bui.Call(bui.pushcall, do_it)) 384 bui.containerwidget(edit=self._root_widget, transition='out_left') 385 386 def _on_my_replay_rename_press(self) -> None: 387 if self._my_replay_selected is None: 388 self._no_replay_selected_error() 389 return 390 c_width = 600 391 c_height = 250 392 assert bui.app.classic is not None 393 uiscale = bui.app.ui_v1.uiscale 394 self._my_replays_rename_window = cnt = bui.containerwidget( 395 scale=( 396 1.8 397 if uiscale is bui.UIScale.SMALL 398 else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.0 399 ), 400 size=(c_width, c_height), 401 transition='in_scale', 402 ) 403 dname = self._get_replay_display_name(self._my_replay_selected) 404 bui.textwidget( 405 parent=cnt, 406 size=(0, 0), 407 h_align='center', 408 v_align='center', 409 text=bui.Lstr( 410 resource=f'{self._r}.renameReplayText', 411 subs=[('${REPLAY}', dname)], 412 ), 413 maxwidth=c_width * 0.8, 414 position=(c_width * 0.5, c_height - 60), 415 ) 416 self._my_replay_rename_text = txt = bui.textwidget( 417 parent=cnt, 418 size=(c_width * 0.8, 40), 419 h_align='left', 420 v_align='center', 421 text=dname, 422 editable=True, 423 description=bui.Lstr(resource=f'{self._r}.replayNameText'), 424 position=(c_width * 0.1, c_height - 140), 425 autoselect=True, 426 maxwidth=c_width * 0.7, 427 max_chars=200, 428 ) 429 cbtn = bui.buttonwidget( 430 parent=cnt, 431 label=bui.Lstr(resource='cancelText'), 432 on_activate_call=bui.Call( 433 lambda c: bui.containerwidget(edit=c, transition='out_scale'), 434 cnt, 435 ), 436 size=(180, 60), 437 position=(30, 30), 438 autoselect=True, 439 ) 440 okb = bui.buttonwidget( 441 parent=cnt, 442 label=bui.Lstr(resource=f'{self._r}.renameText'), 443 size=(180, 60), 444 position=(c_width - 230, 30), 445 on_activate_call=bui.Call( 446 self._rename_my_replay, self._my_replay_selected 447 ), 448 autoselect=True, 449 ) 450 bui.widget(edit=cbtn, right_widget=okb) 451 bui.widget(edit=okb, left_widget=cbtn) 452 bui.textwidget(edit=txt, on_return_press_call=okb.activate) 453 bui.containerwidget(edit=cnt, cancel_button=cbtn, start_button=okb) 454 455 def _rename_my_replay(self, replay: str) -> None: 456 new_name = None 457 try: 458 if not self._my_replay_rename_text: 459 return 460 new_name_raw = cast( 461 str, bui.textwidget(query=self._my_replay_rename_text) 462 ) 463 new_name = new_name_raw + '.brp' 464 465 # Ignore attempts to change it to what it already is 466 # (or what it looks like to the user). 467 if ( 468 replay != new_name 469 and self._get_replay_display_name(replay) != new_name_raw 470 ): 471 old_name_full = (bui.get_replays_dir() + '/' + replay).encode( 472 'utf-8' 473 ) 474 new_name_full = (bui.get_replays_dir() + '/' + new_name).encode( 475 'utf-8' 476 ) 477 # False alarm; bui.textwidget can return non-None val. 478 # pylint: disable=unsupported-membership-test 479 if os.path.exists(new_name_full): 480 bui.getsound('error').play() 481 bui.screenmessage( 482 bui.Lstr( 483 resource=self._r 484 + '.replayRenameErrorAlreadyExistsText' 485 ), 486 color=(1, 0, 0), 487 ) 488 elif any(char in new_name_raw for char in ['/', '\\', ':']): 489 bui.getsound('error').play() 490 bui.screenmessage( 491 bui.Lstr( 492 resource=f'{self._r}.replayRenameErrorInvalidName' 493 ), 494 color=(1, 0, 0), 495 ) 496 else: 497 bui.increment_analytics_count('Replay rename') 498 os.rename(old_name_full, new_name_full) 499 self._refresh_my_replays() 500 bui.getsound('gunCocking').play() 501 except Exception: 502 logging.exception( 503 "Error renaming replay '%s' to '%s'.", replay, new_name 504 ) 505 bui.getsound('error').play() 506 bui.screenmessage( 507 bui.Lstr(resource=f'{self._r}.replayRenameErrorText'), 508 color=(1, 0, 0), 509 ) 510 511 bui.containerwidget( 512 edit=self._my_replays_rename_window, transition='out_scale' 513 ) 514 515 def _on_my_replay_delete_press(self) -> None: 516 from bauiv1lib import confirm 517 518 if self._my_replay_selected is None: 519 self._no_replay_selected_error() 520 return 521 confirm.ConfirmWindow( 522 bui.Lstr( 523 resource=f'{self._r}.deleteConfirmText', 524 subs=[ 525 ( 526 '${REPLAY}', 527 self._get_replay_display_name(self._my_replay_selected), 528 ) 529 ], 530 ), 531 bui.Call(self._delete_replay, self._my_replay_selected), 532 450, 533 150, 534 ) 535 536 def _get_replay_display_name(self, replay: str) -> str: 537 if replay.endswith('.brp'): 538 replay = replay[:-4] 539 if replay == '__lastReplay': 540 return bui.Lstr(resource='replayNameDefaultText').evaluate() 541 return replay 542 543 def _delete_replay(self, replay: str) -> None: 544 try: 545 bui.increment_analytics_count('Replay delete') 546 os.remove((bui.get_replays_dir() + '/' + replay).encode('utf-8')) 547 self._refresh_my_replays() 548 bui.getsound('shieldDown').play() 549 if replay == self._my_replay_selected: 550 self._my_replay_selected = None 551 except Exception: 552 logging.exception("Error deleting replay '%s'.", replay) 553 bui.getsound('error').play() 554 bui.screenmessage( 555 bui.Lstr(resource=f'{self._r}.replayDeleteErrorText'), 556 color=(1, 0, 0), 557 ) 558 559 def _on_my_replay_select(self, replay: str) -> None: 560 self._my_replay_selected = replay 561 562 def _refresh_my_replays(self) -> None: 563 assert self._columnwidget is not None 564 for child in self._columnwidget.get_children(): 565 child.delete() 566 t_scale = 1.6 567 try: 568 names = os.listdir(bui.get_replays_dir()) 569 570 # Ignore random other files in there. 571 names = [n for n in names if n.endswith('.brp')] 572 names.sort(key=lambda x: x.lower()) 573 except Exception: 574 logging.exception('Error listing replays dir.') 575 names = [] 576 577 assert self._my_replays_scroll_width is not None 578 assert self._my_replays_watch_replay_button is not None 579 for i, name in enumerate(names): 580 txt = bui.textwidget( 581 parent=self._columnwidget, 582 size=(self._my_replays_scroll_width / t_scale, 30), 583 selectable=True, 584 color=( 585 (1.0, 1, 0.4) if name == '__lastReplay.brp' else (1, 1, 1) 586 ), 587 always_highlight=True, 588 on_select_call=bui.Call(self._on_my_replay_select, name), 589 on_activate_call=self._my_replays_watch_replay_button.activate, 590 text=self._get_replay_display_name(name), 591 h_align='left', 592 v_align='center', 593 corner_scale=t_scale, 594 maxwidth=(self._my_replays_scroll_width / t_scale) * 0.93, 595 ) 596 if i == 0: 597 bui.widget( 598 edit=txt, 599 up_widget=self._tab_row.tabs[self.TabID.MY_REPLAYS].button, 600 ) 601 self._my_replay_selected = name 602 603 def _save_state(self) -> None: 604 try: 605 sel = self._root_widget.get_selected_child() 606 selected_tab_ids = [ 607 tab_id 608 for tab_id, tab in self._tab_row.tabs.items() 609 if sel == tab.button 610 ] 611 if sel == self._back_button: 612 sel_name = 'Back' 613 elif selected_tab_ids: 614 assert len(selected_tab_ids) == 1 615 sel_name = f'Tab:{selected_tab_ids[0].value}' 616 elif sel == self._tab_container: 617 sel_name = 'TabContainer' 618 else: 619 raise ValueError(f'unrecognized selection {sel}') 620 assert bui.app.classic is not None 621 bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name} 622 except Exception: 623 logging.exception('Error saving state for %s.', self) 624 625 def _restore_state(self) -> None: 626 try: 627 sel: bui.Widget | None 628 assert bui.app.classic is not None 629 sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get( 630 'sel_name' 631 ) 632 assert isinstance(sel_name, (str, type(None))) 633 try: 634 current_tab = self.TabID(bui.app.config.get('Watch Tab')) 635 except ValueError: 636 current_tab = self.TabID.MY_REPLAYS 637 self._set_tab(current_tab) 638 639 if sel_name == 'Back': 640 sel = self._back_button 641 elif sel_name == 'TabContainer': 642 sel = self._tab_container 643 elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): 644 try: 645 sel_tab_id = self.TabID(sel_name.split(':')[-1]) 646 except ValueError: 647 sel_tab_id = self.TabID.MY_REPLAYS 648 sel = self._tab_row.tabs[sel_tab_id].button 649 else: 650 if self._tab_container is not None: 651 sel = self._tab_container 652 else: 653 sel = self._tab_row.tabs[current_tab].button 654 bui.containerwidget(edit=self._root_widget, selected_child=sel) 655 except Exception: 656 logging.exception('Error restoring state for %s.', self)
Window for watching replays.
WatchWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
29 def __init__( 30 self, 31 transition: str | None = 'in_right', 32 origin_widget: bui.Widget | None = None, 33 ): 34 # pylint: disable=too-many-locals 35 from bauiv1lib.tabs import TabRow 36 37 bui.set_analytics_screen('Watch Window') 38 self._tab_data: dict[str, Any] = {} 39 self._my_replays_scroll_width: float | None = None 40 self._my_replays_watch_replay_button: bui.Widget | None = None 41 self._scrollwidget: bui.Widget | None = None 42 self._columnwidget: bui.Widget | None = None 43 self._my_replay_selected: str | None = None 44 self._my_replays_rename_window: bui.Widget | None = None 45 self._my_replay_rename_text: bui.Widget | None = None 46 self._r = 'watchWindow' 47 uiscale = bui.app.ui_v1.uiscale 48 self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040 49 x_inset = 200 if uiscale is bui.UIScale.SMALL else 0 50 self._height = ( 51 570 52 if uiscale is bui.UIScale.SMALL 53 else 670 if uiscale is bui.UIScale.MEDIUM else 800 54 ) 55 self._current_tab: WatchWindow.TabID | None = None 56 extra_top = 20 if uiscale is bui.UIScale.SMALL else 0 57 58 super().__init__( 59 root_widget=bui.containerwidget( 60 size=(self._width, self._height + extra_top), 61 toolbar_visibility=( 62 'menu_minimal' 63 if uiscale is bui.UIScale.SMALL 64 else 'menu_full' 65 ), 66 scale=( 67 1.32 68 if uiscale is bui.UIScale.SMALL 69 else 0.85 if uiscale is bui.UIScale.MEDIUM else 0.65 70 ), 71 stack_offset=( 72 (0, 30) 73 if uiscale is bui.UIScale.SMALL 74 else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) 75 ), 76 ), 77 transition=transition, 78 origin_widget=origin_widget, 79 ) 80 81 if uiscale is bui.UIScale.SMALL: 82 bui.containerwidget( 83 edit=self._root_widget, on_cancel_call=self.main_window_back 84 ) 85 self._back_button = None 86 else: 87 self._back_button = btn = bui.buttonwidget( 88 parent=self._root_widget, 89 autoselect=True, 90 position=(70 + x_inset, self._height - 74), 91 size=(140, 60), 92 scale=1.1, 93 label=bui.Lstr(resource='backText'), 94 button_type='back', 95 on_activate_call=self.main_window_back, 96 ) 97 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 98 bui.buttonwidget( 99 edit=btn, 100 button_type='backSmall', 101 size=(60, 60), 102 label=bui.charstr(bui.SpecialChar.BACK), 103 ) 104 105 bui.textwidget( 106 parent=self._root_widget, 107 position=( 108 self._width * 0.5, 109 self._height - (65 if uiscale is bui.UIScale.SMALL else 38), 110 ), 111 size=(0, 0), 112 color=bui.app.ui_v1.title_color, 113 scale=0.7 if uiscale is bui.UIScale.SMALL else 1.5, 114 h_align='center', 115 v_align='center', 116 text=( 117 '' 118 if uiscale is bui.UIScale.SMALL 119 else bui.Lstr(resource=f'{self._r}.titleText') 120 ), 121 maxwidth=400, 122 ) 123 124 tabdefs = [ 125 ( 126 self.TabID.MY_REPLAYS, 127 bui.Lstr(resource=f'{self._r}.myReplaysText'), 128 ), 129 # (self.TabID.TEST_TAB, bui.Lstr(value='Testing')), 130 ] 131 132 scroll_buffer_h = 130 + 2 * x_inset 133 tab_buffer_h = 750 + 2 * x_inset 134 135 self._tab_row = TabRow( 136 self._root_widget, 137 tabdefs, 138 pos=(tab_buffer_h * 0.5, self._height - 130), 139 size=(self._width - tab_buffer_h, 50), 140 on_select_call=self._set_tab, 141 ) 142 143 first_tab = self._tab_row.tabs[tabdefs[0][0]] 144 last_tab = self._tab_row.tabs[tabdefs[-1][0]] 145 bui.widget( 146 edit=last_tab.button, 147 right_widget=bui.get_special_widget('squad_button'), 148 ) 149 if uiscale is bui.UIScale.SMALL: 150 bbtn = bui.get_special_widget('back_button') 151 bui.widget(edit=first_tab.button, up_widget=bbtn, left_widget=bbtn) 152 153 self._scroll_width = self._width - scroll_buffer_h 154 self._scroll_height = self._height - 180 155 156 # Not actually using a scroll widget anymore; just an image. 157 scroll_left = (self._width - self._scroll_width) * 0.5 158 scroll_bottom = self._height - self._scroll_height - 79 - 48 159 buffer_h = 10 160 buffer_v = 4 161 bui.imagewidget( 162 parent=self._root_widget, 163 position=(scroll_left - buffer_h, scroll_bottom - buffer_v), 164 size=( 165 self._scroll_width + 2 * buffer_h, 166 self._scroll_height + 2 * buffer_v, 167 ), 168 texture=bui.gettexture('scrollWidget'), 169 mesh_transparent=bui.getmesh('softEdgeOutside'), 170 ) 171 self._tab_container: bui.Widget | None = None 172 173 self._restore_state()
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.
175 @override 176 def get_main_window_state(self) -> bui.MainWindowState: 177 # Support recreating our window for back/refresh purposes. 178 cls = type(self) 179 return bui.BasicMainWindowState( 180 create_call=lambda transition, origin_widget: cls( 181 transition=transition, origin_widget=origin_widget 182 ) 183 )
Return a WindowState to recreate this window, if supported.
@override
def
on_main_window_close(self) -> None:
Called before transitioning out a main window.
A good opportunity to save window state/etc.
Inherited Members
- bauiv1._uitypes.MainWindow
- main_window_back_state
- main_window_close
- can_change_main_window
- main_window_back
- main_window_replace
- bauiv1._uitypes.Window
- get_root_widget
class
WatchWindow.TabID(enum.Enum):
23 class TabID(Enum): 24 """Our available tab types.""" 25 26 MY_REPLAYS = 'my_replays' 27 TEST_TAB = 'test_tab'
Our available tab types.
Inherited Members
- enum.Enum
- name
- value