bauiv1lib.gather.manualtab
Defines the manual tab in the gather UI.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Defines the manual tab in the gather UI.""" 4# pylint: disable=too-many-lines 5 6from __future__ import annotations 7 8import logging 9from enum import Enum 10from threading import Thread 11from dataclasses import dataclass 12from typing import TYPE_CHECKING, cast, override 13from bauiv1lib.gather import GatherTab 14 15import bauiv1 as bui 16import bascenev1 as bs 17 18if TYPE_CHECKING: 19 from typing import Any, Callable 20 21 from bauiv1lib.gather import GatherWindow 22 23 24def _safe_set_text( 25 txt: bui.Widget | None, val: str | bui.Lstr, success: bool = True 26) -> None: 27 if txt: 28 bui.textwidget( 29 edit=txt, text=val, color=(0, 1, 0) if success else (1, 1, 0) 30 ) 31 32 33class _HostLookupThread(Thread): 34 """Thread to fetch an addr.""" 35 36 def __init__( 37 self, name: str, port: int, call: Callable[[str | None, int], Any] 38 ): 39 super().__init__() 40 self._name = name 41 self._port = port 42 self._call = call 43 44 @override 45 def run(self) -> None: 46 result: str | None 47 try: 48 import socket 49 50 result = [ 51 item[-1][0] 52 for item in socket.getaddrinfo(self.name, self._port) 53 ][0] 54 except Exception: 55 result = None 56 bui.pushcall( 57 lambda: self._call(result, self._port), from_other_thread=True 58 ) 59 60 61class SubTabType(Enum): 62 """Available sub-tabs.""" 63 64 JOIN_BY_ADDRESS = 'join_by_address' 65 FAVORITES = 'favorites' 66 67 68@dataclass 69class State: 70 """State saved/restored only while the app is running.""" 71 72 sub_tab: SubTabType = SubTabType.JOIN_BY_ADDRESS 73 74 75class ManualGatherTab(GatherTab): 76 """The manual tab in the gather UI""" 77 78 def __init__(self, window: GatherWindow) -> None: 79 super().__init__(window) 80 self._check_button: bui.Widget | None = None 81 self._doing_access_check: bool | None = None 82 self._access_check_count: int | None = None 83 self._sub_tab: SubTabType = SubTabType.JOIN_BY_ADDRESS 84 self._t_addr: bui.Widget | None = None 85 self._t_accessible: bui.Widget | None = None 86 self._t_accessible_extra: bui.Widget | None = None 87 self._access_check_timer: bui.AppTimer | None = None 88 self._checking_state_text: bui.Widget | None = None 89 self._container: bui.Widget | None = None 90 self._join_by_address_text: bui.Widget | None = None 91 self._favorites_text: bui.Widget | None = None 92 self._width: float | None = None 93 self._height: float | None = None 94 self._scroll_width: float | None = None 95 self._scroll_height: float | None = None 96 self._favorites_scroll_width: int | None = None 97 self._favorites_connect_button: bui.Widget | None = None 98 self._scrollwidget: bui.Widget | None = None 99 self._columnwidget: bui.Widget | None = None 100 self._favorite_selected: str | None = None 101 self._favorite_edit_window: bui.Widget | None = None 102 self._party_edit_name_text: bui.Widget | None = None 103 self._party_edit_addr_text: bui.Widget | None = None 104 self._party_edit_port_text: bui.Widget | None = None 105 self._no_parties_added_text: bui.Widget | None = None 106 107 @override 108 def on_activate( 109 self, 110 parent_widget: bui.Widget, 111 tab_button: bui.Widget, 112 region_width: float, 113 region_height: float, 114 region_left: float, 115 region_bottom: float, 116 ) -> bui.Widget: 117 c_width = region_width 118 c_height = region_height - 20 119 120 self._container = bui.containerwidget( 121 parent=parent_widget, 122 position=( 123 region_left, 124 region_bottom + (region_height - c_height) * 0.5, 125 ), 126 size=(c_width, c_height), 127 background=False, 128 selection_loops_to_parent=True, 129 ) 130 v = c_height - 30 131 self._join_by_address_text = bui.textwidget( 132 parent=self._container, 133 position=(c_width * 0.5 - 245, v - 13), 134 color=(0.6, 1.0, 0.6), 135 scale=1.3, 136 size=(200, 30), 137 maxwidth=250, 138 h_align='center', 139 v_align='center', 140 click_activate=True, 141 selectable=True, 142 autoselect=True, 143 on_activate_call=lambda: self._set_sub_tab( 144 SubTabType.JOIN_BY_ADDRESS, 145 region_width, 146 region_height, 147 playsound=True, 148 ), 149 text=bui.Lstr(resource='gatherWindow.manualJoinSectionText'), 150 glow_type='uniform', 151 ) 152 self._favorites_text = bui.textwidget( 153 parent=self._container, 154 position=(c_width * 0.5 + 45, v - 13), 155 color=(0.6, 1.0, 0.6), 156 scale=1.3, 157 size=(200, 30), 158 maxwidth=250, 159 h_align='center', 160 v_align='center', 161 click_activate=True, 162 selectable=True, 163 autoselect=True, 164 on_activate_call=lambda: self._set_sub_tab( 165 SubTabType.FAVORITES, 166 region_width, 167 region_height, 168 playsound=True, 169 ), 170 text=bui.Lstr(resource='gatherWindow.favoritesText'), 171 glow_type='uniform', 172 ) 173 bui.widget(edit=self._join_by_address_text, up_widget=tab_button) 174 bui.widget( 175 edit=self._favorites_text, 176 left_widget=self._join_by_address_text, 177 up_widget=tab_button, 178 ) 179 bui.widget(edit=tab_button, down_widget=self._favorites_text) 180 bui.widget( 181 edit=self._join_by_address_text, right_widget=self._favorites_text 182 ) 183 self._set_sub_tab(self._sub_tab, region_width, region_height) 184 185 return self._container 186 187 @override 188 def save_state(self) -> None: 189 assert bui.app.classic is not None 190 bui.app.ui_v1.window_states[type(self)] = State(sub_tab=self._sub_tab) 191 192 @override 193 def restore_state(self) -> None: 194 assert bui.app.classic is not None 195 state = bui.app.ui_v1.window_states.get(type(self)) 196 if state is None: 197 state = State() 198 assert isinstance(state, State) 199 self._sub_tab = state.sub_tab 200 201 def _set_sub_tab( 202 self, 203 value: SubTabType, 204 region_width: float, 205 region_height: float, 206 playsound: bool = False, 207 ) -> None: 208 assert self._container 209 if playsound: 210 bui.getsound('click01').play() 211 212 self._sub_tab = value 213 active_color = (0.6, 1.0, 0.6) 214 inactive_color = (0.5, 0.4, 0.5) 215 bui.textwidget( 216 edit=self._join_by_address_text, 217 color=( 218 active_color 219 if value is SubTabType.JOIN_BY_ADDRESS 220 else inactive_color 221 ), 222 ) 223 bui.textwidget( 224 edit=self._favorites_text, 225 color=( 226 active_color 227 if value is SubTabType.FAVORITES 228 else inactive_color 229 ), 230 ) 231 232 # Clear anything existing in the old sub-tab. 233 for widget in self._container.get_children(): 234 if widget and widget not in { 235 self._favorites_text, 236 self._join_by_address_text, 237 }: 238 widget.delete() 239 240 if value is SubTabType.JOIN_BY_ADDRESS: 241 self._build_join_by_address_tab(region_width, region_height) 242 243 if value is SubTabType.FAVORITES: 244 self._build_favorites_tab(region_width, region_height) 245 246 # The old manual tab 247 def _build_join_by_address_tab( 248 self, region_width: float, region_height: float 249 ) -> None: 250 c_width = region_width 251 c_height = region_height - 20 252 last_addr = bui.app.config.get('Last Manual Party Connect Address', '') 253 last_port = bui.app.config.get('Last Manual Party Connect Port', 43210) 254 v = c_height - 70 255 v -= 70 256 bui.textwidget( 257 parent=self._container, 258 position=(c_width * 0.5 - 260 - 50, v), 259 color=(0.6, 1.0, 0.6), 260 scale=1.0, 261 size=(0, 0), 262 maxwidth=130, 263 h_align='right', 264 v_align='center', 265 text=bui.Lstr(resource='gatherWindow.' 'manualAddressText'), 266 ) 267 txt = bui.textwidget( 268 parent=self._container, 269 editable=True, 270 description=bui.Lstr(resource='gatherWindow.' 'manualAddressText'), 271 position=(c_width * 0.5 - 240 - 50, v - 30), 272 text=last_addr, 273 autoselect=True, 274 v_align='center', 275 scale=1.0, 276 maxwidth=380, 277 size=(420, 60), 278 ) 279 assert self._join_by_address_text is not None 280 bui.widget(edit=self._join_by_address_text, down_widget=txt) 281 assert self._favorites_text is not None 282 bui.widget(edit=self._favorites_text, down_widget=txt) 283 bui.textwidget( 284 parent=self._container, 285 position=(c_width * 0.5 - 260 + 490, v), 286 color=(0.6, 1.0, 0.6), 287 scale=1.0, 288 size=(0, 0), 289 maxwidth=80, 290 h_align='right', 291 v_align='center', 292 text=bui.Lstr(resource='gatherWindow.' 'portText'), 293 ) 294 txt2 = bui.textwidget( 295 parent=self._container, 296 editable=True, 297 description=bui.Lstr(resource='gatherWindow.' 'portText'), 298 text=str(last_port), 299 autoselect=True, 300 max_chars=5, 301 position=(c_width * 0.5 - 240 + 490, v - 30), 302 v_align='center', 303 scale=1.0, 304 size=(170, 60), 305 ) 306 307 v -= 110 308 309 btn = bui.buttonwidget( 310 parent=self._container, 311 size=(300, 70), 312 label=bui.Lstr(resource='gatherWindow.' 'manualConnectText'), 313 position=(c_width * 0.5 - 300, v), 314 autoselect=True, 315 on_activate_call=bui.Call(self._connect, txt, txt2), 316 ) 317 savebutton = bui.buttonwidget( 318 parent=self._container, 319 size=(300, 70), 320 label=bui.Lstr(resource='gatherWindow.favoritesSaveText'), 321 position=(c_width * 0.5 - 240 + 490 - 200, v), 322 autoselect=True, 323 on_activate_call=bui.Call(self._save_server, txt, txt2), 324 ) 325 bui.widget(edit=btn, right_widget=savebutton) 326 bui.widget(edit=savebutton, left_widget=btn, up_widget=txt2) 327 bui.textwidget(edit=txt, on_return_press_call=btn.activate) 328 bui.textwidget(edit=txt2, on_return_press_call=btn.activate) 329 v -= 45 330 331 self._check_button = bui.textwidget( 332 parent=self._container, 333 size=(250, 60), 334 text=bui.Lstr(resource='gatherWindow.showMyAddressText'), 335 v_align='center', 336 h_align='center', 337 click_activate=True, 338 position=(c_width * 0.5 - 125, v - 30), 339 autoselect=True, 340 color=(0.5, 0.9, 0.5), 341 scale=0.8, 342 selectable=True, 343 on_activate_call=bui.Call( 344 self._on_show_my_address_button_press, 345 v, 346 self._container, 347 c_width, 348 ), 349 glow_type='uniform', 350 ) 351 bui.widget(edit=self._check_button, up_widget=btn) 352 353 # Tab containing saved favorite addresses 354 def _build_favorites_tab( 355 self, region_width: float, region_height: float 356 ) -> None: 357 c_height = region_height - 20 358 v = c_height - 35 - 25 - 30 359 360 assert bui.app.classic is not None 361 uiscale = bui.app.ui_v1.uiscale 362 # self._width = 1240 if uiscale is bui.UIScale.SMALL else 1040 363 self._width = region_width 364 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 365 self._height = ( 366 578 367 if uiscale is bui.UIScale.SMALL 368 else 670 if uiscale is bui.UIScale.MEDIUM else 800 369 ) 370 371 self._scroll_width = self._width - 130 + 2 * x_inset 372 self._scroll_height = self._height - 180 373 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 374 375 c_height = self._scroll_height - 20 376 sub_scroll_height = c_height - 63 377 self._favorites_scroll_width = sub_scroll_width = ( 378 680 if uiscale is bui.UIScale.SMALL else 640 379 ) 380 381 v = c_height - 30 382 383 b_width = 140 if uiscale is bui.UIScale.SMALL else 178 384 b_height = ( 385 107 386 if uiscale is bui.UIScale.SMALL 387 else 142 if uiscale is bui.UIScale.MEDIUM else 190 388 ) 389 b_space_extra = ( 390 0 391 if uiscale is bui.UIScale.SMALL 392 else -2 if uiscale is bui.UIScale.MEDIUM else -5 393 ) 394 395 btnv = ( 396 c_height 397 - ( 398 48 399 if uiscale is bui.UIScale.SMALL 400 else 45 if uiscale is bui.UIScale.MEDIUM else 40 401 ) 402 - b_height 403 ) 404 405 self._favorites_connect_button = btn1 = bui.buttonwidget( 406 parent=self._container, 407 size=(b_width, b_height), 408 position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv), 409 button_type='square', 410 color=(0.6, 0.53, 0.63), 411 textcolor=(0.75, 0.7, 0.8), 412 on_activate_call=self._on_favorites_connect_press, 413 text_scale=1.0 if uiscale is bui.UIScale.SMALL else 1.2, 414 label=bui.Lstr(resource='gatherWindow.manualConnectText'), 415 autoselect=True, 416 ) 417 if uiscale is bui.UIScale.SMALL: 418 bui.widget( 419 edit=btn1, 420 left_widget=bui.get_special_widget('back_button'), 421 ) 422 btnv -= b_height + b_space_extra 423 bui.buttonwidget( 424 parent=self._container, 425 size=(b_width, b_height), 426 position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv), 427 button_type='square', 428 color=(0.6, 0.53, 0.63), 429 textcolor=(0.75, 0.7, 0.8), 430 on_activate_call=self._on_favorites_edit_press, 431 text_scale=1.0 if uiscale is bui.UIScale.SMALL else 1.2, 432 label=bui.Lstr(resource='editText'), 433 autoselect=True, 434 ) 435 btnv -= b_height + b_space_extra 436 bui.buttonwidget( 437 parent=self._container, 438 size=(b_width, b_height), 439 position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv), 440 button_type='square', 441 color=(0.6, 0.53, 0.63), 442 textcolor=(0.75, 0.7, 0.8), 443 on_activate_call=self._on_favorite_delete_press, 444 text_scale=1.0 if uiscale is bui.UIScale.SMALL else 1.2, 445 label=bui.Lstr(resource='deleteText'), 446 autoselect=True, 447 ) 448 449 v -= sub_scroll_height + 23 450 self._scrollwidget = scrlw = bui.scrollwidget( 451 parent=self._container, 452 position=(290 if uiscale is bui.UIScale.SMALL else 225, v), 453 size=(sub_scroll_width, sub_scroll_height), 454 claims_left_right=True, 455 ) 456 bui.widget( 457 edit=self._favorites_connect_button, right_widget=self._scrollwidget 458 ) 459 self._columnwidget = bui.columnwidget( 460 parent=scrlw, 461 left_border=10, 462 border=2, 463 margin=0, 464 claims_left_right=True, 465 ) 466 467 self._no_parties_added_text = bui.textwidget( 468 parent=self._container, 469 size=(0, 0), 470 h_align='center', 471 v_align='center', 472 text='', 473 color=(0.6, 0.6, 0.6), 474 scale=1.2, 475 position=( 476 ( 477 (240 if uiscale is bui.UIScale.SMALL else 225) 478 + sub_scroll_width * 0.5 479 ), 480 v + sub_scroll_height * 0.5, 481 ), 482 glow_type='uniform', 483 ) 484 485 self._favorite_selected = None 486 self._refresh_favorites() 487 488 def _no_favorite_selected_error(self) -> None: 489 bui.screenmessage( 490 bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) 491 ) 492 bui.getsound('error').play() 493 494 def _on_favorites_connect_press(self) -> None: 495 if self._favorite_selected is None: 496 self._no_favorite_selected_error() 497 498 else: 499 config = bui.app.config['Saved Servers'][self._favorite_selected] 500 _HostLookupThread( 501 name=config['addr'], 502 port=config['port'], 503 call=bui.WeakCall(self._host_lookup_result), 504 ).start() 505 506 def _on_favorites_edit_press(self) -> None: 507 if self._favorite_selected is None: 508 self._no_favorite_selected_error() 509 return 510 511 c_width = 600 512 c_height = 310 513 assert bui.app.classic is not None 514 uiscale = bui.app.ui_v1.uiscale 515 self._favorite_edit_window = cnt = bui.containerwidget( 516 scale=( 517 1.8 518 if uiscale is bui.UIScale.SMALL 519 else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.0 520 ), 521 size=(c_width, c_height), 522 transition='in_scale', 523 ) 524 525 bui.textwidget( 526 parent=cnt, 527 size=(0, 0), 528 h_align='center', 529 v_align='center', 530 text=bui.Lstr(resource='editText'), 531 color=(0.6, 1.0, 0.6), 532 maxwidth=c_width * 0.8, 533 position=(c_width * 0.5, c_height - 60), 534 ) 535 536 bui.textwidget( 537 parent=cnt, 538 position=(c_width * 0.2 - 15, c_height - 120), 539 color=(0.6, 1.0, 0.6), 540 scale=1.0, 541 size=(0, 0), 542 maxwidth=60, 543 h_align='right', 544 v_align='center', 545 text=bui.Lstr(resource='nameText'), 546 ) 547 548 self._party_edit_name_text = bui.textwidget( 549 parent=cnt, 550 size=(c_width * 0.7, 40), 551 h_align='left', 552 v_align='center', 553 text=bui.app.config['Saved Servers'][self._favorite_selected][ 554 'name' 555 ], 556 editable=True, 557 description=bui.Lstr(resource='nameText'), 558 position=(c_width * 0.2, c_height - 140), 559 autoselect=True, 560 maxwidth=c_width * 0.6, 561 max_chars=200, 562 ) 563 564 bui.textwidget( 565 parent=cnt, 566 position=(c_width * 0.2 - 15, c_height - 180), 567 color=(0.6, 1.0, 0.6), 568 scale=1.0, 569 size=(0, 0), 570 maxwidth=60, 571 h_align='right', 572 v_align='center', 573 text=bui.Lstr(resource='gatherWindow.' 'manualAddressText'), 574 ) 575 576 self._party_edit_addr_text = bui.textwidget( 577 parent=cnt, 578 size=(c_width * 0.4, 40), 579 h_align='left', 580 v_align='center', 581 text=bui.app.config['Saved Servers'][self._favorite_selected][ 582 'addr' 583 ], 584 editable=True, 585 description=bui.Lstr(resource='gatherWindow.manualAddressText'), 586 position=(c_width * 0.2, c_height - 200), 587 autoselect=True, 588 maxwidth=c_width * 0.35, 589 max_chars=200, 590 ) 591 592 bui.textwidget( 593 parent=cnt, 594 position=(c_width * 0.7 - 10, c_height - 180), 595 color=(0.6, 1.0, 0.6), 596 scale=1.0, 597 size=(0, 0), 598 maxwidth=45, 599 h_align='right', 600 v_align='center', 601 text=bui.Lstr(resource='gatherWindow.' 'portText'), 602 ) 603 604 self._party_edit_port_text = bui.textwidget( 605 parent=cnt, 606 size=(c_width * 0.2, 40), 607 h_align='left', 608 v_align='center', 609 text=str( 610 bui.app.config['Saved Servers'][self._favorite_selected]['port'] 611 ), 612 editable=True, 613 description=bui.Lstr(resource='gatherWindow.portText'), 614 position=(c_width * 0.7, c_height - 200), 615 autoselect=True, 616 maxwidth=c_width * 0.2, 617 max_chars=6, 618 ) 619 cbtn = bui.buttonwidget( 620 parent=cnt, 621 label=bui.Lstr(resource='cancelText'), 622 on_activate_call=bui.Call( 623 lambda c: bui.containerwidget(edit=c, transition='out_scale'), 624 cnt, 625 ), 626 size=(180, 60), 627 position=(30, 30), 628 autoselect=True, 629 ) 630 okb = bui.buttonwidget( 631 parent=cnt, 632 label=bui.Lstr(resource='saveText'), 633 size=(180, 60), 634 position=(c_width - 230, 30), 635 on_activate_call=bui.Call(self._edit_saved_party), 636 autoselect=True, 637 ) 638 bui.widget(edit=cbtn, right_widget=okb) 639 bui.widget(edit=okb, left_widget=cbtn) 640 bui.containerwidget(edit=cnt, cancel_button=cbtn, start_button=okb) 641 642 def _edit_saved_party(self) -> None: 643 server = self._favorite_selected 644 if self._favorite_selected is None: 645 self._no_favorite_selected_error() 646 return 647 if not self._party_edit_name_text or not self._party_edit_addr_text: 648 return 649 new_name_raw = cast( 650 str, bui.textwidget(query=self._party_edit_name_text) 651 ) 652 new_addr_raw = cast( 653 str, bui.textwidget(query=self._party_edit_addr_text) 654 ) 655 new_port_raw = cast( 656 str, bui.textwidget(query=self._party_edit_port_text) 657 ) 658 bui.app.config['Saved Servers'][server]['name'] = new_name_raw 659 bui.app.config['Saved Servers'][server]['addr'] = new_addr_raw 660 try: 661 bui.app.config['Saved Servers'][server]['port'] = int(new_port_raw) 662 except ValueError: 663 # Notify about incorrect port? I'm lazy; simply leave old value. 664 pass 665 bui.app.config.commit() 666 bui.getsound('gunCocking').play() 667 self._refresh_favorites() 668 669 bui.containerwidget( 670 edit=self._favorite_edit_window, transition='out_scale' 671 ) 672 673 def _on_favorite_delete_press(self) -> None: 674 from bauiv1lib import confirm 675 676 if self._favorite_selected is None: 677 self._no_favorite_selected_error() 678 return 679 confirm.ConfirmWindow( 680 bui.Lstr( 681 resource='gameListWindow.deleteConfirmText', 682 subs=[ 683 ( 684 '${LIST}', 685 bui.app.config['Saved Servers'][ 686 self._favorite_selected 687 ]['name'], 688 ) 689 ], 690 ), 691 self._delete_saved_party, 692 450, 693 150, 694 ) 695 696 def _delete_saved_party(self) -> None: 697 if self._favorite_selected is None: 698 self._no_favorite_selected_error() 699 return 700 config = bui.app.config['Saved Servers'] 701 del config[self._favorite_selected] 702 self._favorite_selected = None 703 bui.app.config.commit() 704 bui.getsound('shieldDown').play() 705 self._refresh_favorites() 706 707 def _on_favorite_select(self, server: str) -> None: 708 self._favorite_selected = server 709 710 def _refresh_favorites(self) -> None: 711 assert self._columnwidget is not None 712 for child in self._columnwidget.get_children(): 713 child.delete() 714 t_scale = 1.6 715 716 config = bui.app.config 717 if 'Saved Servers' in config: 718 servers = config['Saved Servers'] 719 720 else: 721 servers = [] 722 723 assert self._favorites_scroll_width is not None 724 assert self._favorites_connect_button is not None 725 726 bui.textwidget( 727 edit=self._no_parties_added_text, 728 text='', 729 ) 730 num_of_fav = 0 731 for i, server in enumerate(servers): 732 txt = bui.textwidget( 733 parent=self._columnwidget, 734 size=(self._favorites_scroll_width / t_scale, 30), 735 selectable=True, 736 color=(1.0, 1, 0.4), 737 always_highlight=True, 738 on_select_call=bui.Call(self._on_favorite_select, server), 739 on_activate_call=self._favorites_connect_button.activate, 740 text=( 741 config['Saved Servers'][server]['name'] 742 if config['Saved Servers'][server]['name'] != '' 743 else config['Saved Servers'][server]['addr'] 744 + ' ' 745 + str(config['Saved Servers'][server]['port']) 746 ), 747 h_align='left', 748 v_align='center', 749 corner_scale=t_scale, 750 maxwidth=(self._favorites_scroll_width / t_scale) * 0.93, 751 ) 752 if i == 0: 753 bui.widget(edit=txt, up_widget=self._favorites_text) 754 self._favorite_selected = server 755 bui.widget( 756 edit=txt, 757 left_widget=self._favorites_connect_button, 758 right_widget=txt, 759 ) 760 num_of_fav = num_of_fav + 1 761 762 # If there's no servers, allow selecting out of the scroll area 763 bui.containerwidget( 764 edit=self._scrollwidget, 765 claims_left_right=bool(servers), 766 claims_up_down=bool(servers), 767 ) 768 assert self._scrollwidget is not None 769 bui.widget( 770 edit=self._scrollwidget, 771 up_widget=self._favorites_text, 772 left_widget=self._favorites_connect_button, 773 ) 774 if num_of_fav == 0: 775 bui.textwidget( 776 edit=self._no_parties_added_text, 777 text=bui.Lstr(resource='gatherWindow.noPartiesAddedText'), 778 ) 779 780 @override 781 def on_deactivate(self) -> None: 782 self._access_check_timer = None 783 784 def _connect( 785 self, textwidget: bui.Widget, port_textwidget: bui.Widget 786 ) -> None: 787 addr = cast(str, bui.textwidget(query=textwidget)) 788 if addr == '': 789 bui.screenmessage( 790 bui.Lstr(resource='internal.invalidAddressErrorText'), 791 color=(1, 0, 0), 792 ) 793 bui.getsound('error').play() 794 return 795 try: 796 port = int(cast(str, bui.textwidget(query=port_textwidget))) 797 except ValueError: 798 port = -1 799 if port > 65535 or port < 0: 800 bui.screenmessage( 801 bui.Lstr(resource='internal.invalidPortErrorText'), 802 color=(1, 0, 0), 803 ) 804 bui.getsound('error').play() 805 return 806 807 _HostLookupThread( 808 name=addr, port=port, call=bui.WeakCall(self._host_lookup_result) 809 ).start() 810 811 def _save_server( 812 self, textwidget: bui.Widget, port_textwidget: bui.Widget 813 ) -> None: 814 addr = cast(str, bui.textwidget(query=textwidget)) 815 if addr == '': 816 bui.screenmessage( 817 bui.Lstr(resource='internal.invalidAddressErrorText'), 818 color=(1, 0, 0), 819 ) 820 bui.getsound('error').play() 821 return 822 try: 823 port = int(cast(str, bui.textwidget(query=port_textwidget))) 824 except ValueError: 825 port = -1 826 if port > 65535 or port < 0: 827 bui.screenmessage( 828 bui.Lstr(resource='internal.invalidPortErrorText'), 829 color=(1, 0, 0), 830 ) 831 bui.getsound('error').play() 832 return 833 config = bui.app.config 834 835 if addr: 836 if not isinstance(config.get('Saved Servers'), dict): 837 config['Saved Servers'] = {} 838 config['Saved Servers'][f'{addr}@{port}'] = { 839 'addr': addr, 840 'port': port, 841 'name': addr, 842 } 843 config.commit() 844 bui.getsound('gunCocking').play() 845 bui.screenmessage( 846 bui.Lstr( 847 resource='addedToFavoritesText', subs=[('${NAME}', addr)] 848 ), 849 color=(0, 1, 0), 850 ) 851 else: 852 bui.screenmessage( 853 bui.Lstr(resource='internal.invalidAddressErrorText'), 854 color=(1, 0, 0), 855 ) 856 bui.getsound('error').play() 857 858 def _host_lookup_result( 859 self, resolved_address: str | None, port: int 860 ) -> None: 861 if resolved_address is None: 862 bui.screenmessage( 863 bui.Lstr(resource='internal.unableToResolveHostText'), 864 color=(1, 0, 0), 865 ) 866 bui.getsound('error').play() 867 else: 868 # Store for later. 869 config = bui.app.config 870 config['Last Manual Party Connect Address'] = resolved_address 871 config['Last Manual Party Connect Port'] = port 872 config.commit() 873 bs.connect_to_party(resolved_address, port=port) 874 875 def _run_addr_fetch(self) -> None: 876 try: 877 # FIXME: Update this to work with IPv6. 878 import socket 879 880 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 881 sock.connect(('8.8.8.8', 80)) 882 val = sock.getsockname()[0] 883 sock.close() 884 bui.pushcall( 885 bui.Call( 886 _safe_set_text, 887 self._checking_state_text, 888 val, 889 ), 890 from_other_thread=True, 891 ) 892 except Exception as exc: 893 from efro.error import is_udp_communication_error 894 895 if is_udp_communication_error(exc): 896 bui.pushcall( 897 bui.Call( 898 _safe_set_text, 899 self._checking_state_text, 900 bui.Lstr(resource='gatherWindow.' 'noConnectionText'), 901 False, 902 ), 903 from_other_thread=True, 904 ) 905 else: 906 bui.pushcall( 907 bui.Call( 908 _safe_set_text, 909 self._checking_state_text, 910 bui.Lstr( 911 resource='gatherWindow.' 'addressFetchErrorText' 912 ), 913 False, 914 ), 915 from_other_thread=True, 916 ) 917 logging.exception('Error in AddrFetchThread.') 918 919 def _on_show_my_address_button_press( 920 self, v2: float, container: bui.Widget | None, c_width: float 921 ) -> None: 922 if not container: 923 return 924 925 tscl = 0.85 926 tspc = 25 927 928 bui.getsound('swish').play() 929 bui.textwidget( 930 parent=container, 931 position=(c_width * 0.5 - 10, v2), 932 color=(0.6, 1.0, 0.6), 933 scale=tscl, 934 size=(0, 0), 935 maxwidth=c_width * 0.45, 936 flatness=1.0, 937 h_align='right', 938 v_align='center', 939 text=bui.Lstr( 940 resource='gatherWindow.' 'manualYourLocalAddressText' 941 ), 942 ) 943 self._checking_state_text = bui.textwidget( 944 parent=container, 945 position=(c_width * 0.5, v2), 946 color=(0.5, 0.5, 0.5), 947 scale=tscl, 948 size=(0, 0), 949 maxwidth=c_width * 0.45, 950 flatness=1.0, 951 h_align='left', 952 v_align='center', 953 text=bui.Lstr(resource='gatherWindow.' 'checkingText'), 954 ) 955 956 Thread(target=self._run_addr_fetch).start() 957 958 v2 -= tspc 959 bui.textwidget( 960 parent=container, 961 position=(c_width * 0.5 - 10, v2), 962 color=(0.6, 1.0, 0.6), 963 scale=tscl, 964 size=(0, 0), 965 maxwidth=c_width * 0.45, 966 flatness=1.0, 967 h_align='right', 968 v_align='center', 969 text=bui.Lstr( 970 resource='gatherWindow.' 'manualYourAddressFromInternetText' 971 ), 972 ) 973 974 t_addr = bui.textwidget( 975 parent=container, 976 position=(c_width * 0.5, v2), 977 color=(0.5, 0.5, 0.5), 978 scale=tscl, 979 size=(0, 0), 980 maxwidth=c_width * 0.45, 981 h_align='left', 982 v_align='center', 983 flatness=1.0, 984 text=bui.Lstr(resource='gatherWindow.' 'checkingText'), 985 ) 986 v2 -= tspc 987 bui.textwidget( 988 parent=container, 989 position=(c_width * 0.5 - 10, v2), 990 color=(0.6, 1.0, 0.6), 991 scale=tscl, 992 size=(0, 0), 993 maxwidth=c_width * 0.45, 994 flatness=1.0, 995 h_align='right', 996 v_align='center', 997 text=bui.Lstr( 998 resource='gatherWindow.' 'manualJoinableFromInternetText' 999 ), 1000 ) 1001 1002 t_accessible = bui.textwidget( 1003 parent=container, 1004 position=(c_width * 0.5, v2), 1005 color=(0.5, 0.5, 0.5), 1006 scale=tscl, 1007 size=(0, 0), 1008 maxwidth=c_width * 0.45, 1009 flatness=1.0, 1010 h_align='left', 1011 v_align='center', 1012 text=bui.Lstr(resource='gatherWindow.' 'checkingText'), 1013 ) 1014 v2 -= 28 1015 t_accessible_extra = bui.textwidget( 1016 parent=container, 1017 position=(c_width * 0.5, v2), 1018 color=(1, 0.5, 0.2), 1019 scale=0.7, 1020 size=(0, 0), 1021 maxwidth=c_width * 0.9, 1022 flatness=1.0, 1023 h_align='center', 1024 v_align='center', 1025 text='', 1026 ) 1027 1028 self._doing_access_check = False 1029 self._access_check_count = 0 # Cap our refreshes eventually. 1030 self._access_check_timer = bui.AppTimer( 1031 10.0, 1032 bui.WeakCall( 1033 self._access_check_update, 1034 t_addr, 1035 t_accessible, 1036 t_accessible_extra, 1037 ), 1038 repeat=True, 1039 ) 1040 1041 # Kick initial off. 1042 self._access_check_update(t_addr, t_accessible, t_accessible_extra) 1043 if self._check_button: 1044 self._check_button.delete() 1045 1046 def _access_check_update( 1047 self, 1048 t_addr: bui.Widget, 1049 t_accessible: bui.Widget, 1050 t_accessible_extra: bui.Widget, 1051 ) -> None: 1052 assert bui.app.classic is not None 1053 # If we don't have an outstanding query, start one.. 1054 assert self._doing_access_check is not None 1055 assert self._access_check_count is not None 1056 if not self._doing_access_check and self._access_check_count < 100: 1057 self._doing_access_check = True 1058 self._access_check_count += 1 1059 self._t_addr = t_addr 1060 self._t_accessible = t_accessible 1061 self._t_accessible_extra = t_accessible_extra 1062 bui.app.classic.master_server_v1_get( 1063 'bsAccessCheck', 1064 {'b': bui.app.env.engine_build_number}, 1065 callback=bui.WeakCall(self._on_accessible_response), 1066 ) 1067 1068 def _on_accessible_response(self, data: dict[str, Any] | None) -> None: 1069 t_addr = self._t_addr 1070 t_accessible = self._t_accessible 1071 t_accessible_extra = self._t_accessible_extra 1072 self._doing_access_check = False 1073 color_bad = (1, 1, 0) 1074 color_good = (0, 1, 0) 1075 if data is None or 'address' not in data or 'accessible' not in data: 1076 if t_addr: 1077 bui.textwidget( 1078 edit=t_addr, 1079 text=bui.Lstr(resource='gatherWindow.' 'noConnectionText'), 1080 color=color_bad, 1081 ) 1082 if t_accessible: 1083 bui.textwidget( 1084 edit=t_accessible, 1085 text=bui.Lstr(resource='gatherWindow.' 'noConnectionText'), 1086 color=color_bad, 1087 ) 1088 if t_accessible_extra: 1089 bui.textwidget( 1090 edit=t_accessible_extra, text='', color=color_bad 1091 ) 1092 return 1093 if t_addr: 1094 bui.textwidget(edit=t_addr, text=data['address'], color=color_good) 1095 if t_accessible: 1096 if data['accessible']: 1097 bui.textwidget( 1098 edit=t_accessible, 1099 text=bui.Lstr( 1100 resource='gatherWindow.' 'manualJoinableYesText' 1101 ), 1102 color=color_good, 1103 ) 1104 if t_accessible_extra: 1105 bui.textwidget( 1106 edit=t_accessible_extra, text='', color=color_good 1107 ) 1108 else: 1109 bui.textwidget( 1110 edit=t_accessible, 1111 text=bui.Lstr( 1112 resource='gatherWindow.' 1113 'manualJoinableNoWithAsteriskText' 1114 ), 1115 color=color_bad, 1116 ) 1117 if t_accessible_extra: 1118 bui.textwidget( 1119 edit=t_accessible_extra, 1120 text=bui.Lstr( 1121 resource='gatherWindow.' 1122 'manualRouterForwardingText', 1123 subs=[ 1124 ('${PORT}', str(bs.get_game_port())), 1125 ], 1126 ), 1127 color=color_bad, 1128 )
class
SubTabType(enum.Enum):
62class SubTabType(Enum): 63 """Available sub-tabs.""" 64 65 JOIN_BY_ADDRESS = 'join_by_address' 66 FAVORITES = 'favorites'
Available sub-tabs.
JOIN_BY_ADDRESS =
<SubTabType.JOIN_BY_ADDRESS: 'join_by_address'>
FAVORITES =
<SubTabType.FAVORITES: 'favorites'>
Inherited Members
- enum.Enum
- name
- value
@dataclass
class
State:
69@dataclass 70class State: 71 """State saved/restored only while the app is running.""" 72 73 sub_tab: SubTabType = SubTabType.JOIN_BY_ADDRESS
State saved/restored only while the app is running.
State( sub_tab: SubTabType = <SubTabType.JOIN_BY_ADDRESS: 'join_by_address'>)
76class ManualGatherTab(GatherTab): 77 """The manual tab in the gather UI""" 78 79 def __init__(self, window: GatherWindow) -> None: 80 super().__init__(window) 81 self._check_button: bui.Widget | None = None 82 self._doing_access_check: bool | None = None 83 self._access_check_count: int | None = None 84 self._sub_tab: SubTabType = SubTabType.JOIN_BY_ADDRESS 85 self._t_addr: bui.Widget | None = None 86 self._t_accessible: bui.Widget | None = None 87 self._t_accessible_extra: bui.Widget | None = None 88 self._access_check_timer: bui.AppTimer | None = None 89 self._checking_state_text: bui.Widget | None = None 90 self._container: bui.Widget | None = None 91 self._join_by_address_text: bui.Widget | None = None 92 self._favorites_text: bui.Widget | None = None 93 self._width: float | None = None 94 self._height: float | None = None 95 self._scroll_width: float | None = None 96 self._scroll_height: float | None = None 97 self._favorites_scroll_width: int | None = None 98 self._favorites_connect_button: bui.Widget | None = None 99 self._scrollwidget: bui.Widget | None = None 100 self._columnwidget: bui.Widget | None = None 101 self._favorite_selected: str | None = None 102 self._favorite_edit_window: bui.Widget | None = None 103 self._party_edit_name_text: bui.Widget | None = None 104 self._party_edit_addr_text: bui.Widget | None = None 105 self._party_edit_port_text: bui.Widget | None = None 106 self._no_parties_added_text: bui.Widget | None = None 107 108 @override 109 def on_activate( 110 self, 111 parent_widget: bui.Widget, 112 tab_button: bui.Widget, 113 region_width: float, 114 region_height: float, 115 region_left: float, 116 region_bottom: float, 117 ) -> bui.Widget: 118 c_width = region_width 119 c_height = region_height - 20 120 121 self._container = bui.containerwidget( 122 parent=parent_widget, 123 position=( 124 region_left, 125 region_bottom + (region_height - c_height) * 0.5, 126 ), 127 size=(c_width, c_height), 128 background=False, 129 selection_loops_to_parent=True, 130 ) 131 v = c_height - 30 132 self._join_by_address_text = bui.textwidget( 133 parent=self._container, 134 position=(c_width * 0.5 - 245, v - 13), 135 color=(0.6, 1.0, 0.6), 136 scale=1.3, 137 size=(200, 30), 138 maxwidth=250, 139 h_align='center', 140 v_align='center', 141 click_activate=True, 142 selectable=True, 143 autoselect=True, 144 on_activate_call=lambda: self._set_sub_tab( 145 SubTabType.JOIN_BY_ADDRESS, 146 region_width, 147 region_height, 148 playsound=True, 149 ), 150 text=bui.Lstr(resource='gatherWindow.manualJoinSectionText'), 151 glow_type='uniform', 152 ) 153 self._favorites_text = bui.textwidget( 154 parent=self._container, 155 position=(c_width * 0.5 + 45, v - 13), 156 color=(0.6, 1.0, 0.6), 157 scale=1.3, 158 size=(200, 30), 159 maxwidth=250, 160 h_align='center', 161 v_align='center', 162 click_activate=True, 163 selectable=True, 164 autoselect=True, 165 on_activate_call=lambda: self._set_sub_tab( 166 SubTabType.FAVORITES, 167 region_width, 168 region_height, 169 playsound=True, 170 ), 171 text=bui.Lstr(resource='gatherWindow.favoritesText'), 172 glow_type='uniform', 173 ) 174 bui.widget(edit=self._join_by_address_text, up_widget=tab_button) 175 bui.widget( 176 edit=self._favorites_text, 177 left_widget=self._join_by_address_text, 178 up_widget=tab_button, 179 ) 180 bui.widget(edit=tab_button, down_widget=self._favorites_text) 181 bui.widget( 182 edit=self._join_by_address_text, right_widget=self._favorites_text 183 ) 184 self._set_sub_tab(self._sub_tab, region_width, region_height) 185 186 return self._container 187 188 @override 189 def save_state(self) -> None: 190 assert bui.app.classic is not None 191 bui.app.ui_v1.window_states[type(self)] = State(sub_tab=self._sub_tab) 192 193 @override 194 def restore_state(self) -> None: 195 assert bui.app.classic is not None 196 state = bui.app.ui_v1.window_states.get(type(self)) 197 if state is None: 198 state = State() 199 assert isinstance(state, State) 200 self._sub_tab = state.sub_tab 201 202 def _set_sub_tab( 203 self, 204 value: SubTabType, 205 region_width: float, 206 region_height: float, 207 playsound: bool = False, 208 ) -> None: 209 assert self._container 210 if playsound: 211 bui.getsound('click01').play() 212 213 self._sub_tab = value 214 active_color = (0.6, 1.0, 0.6) 215 inactive_color = (0.5, 0.4, 0.5) 216 bui.textwidget( 217 edit=self._join_by_address_text, 218 color=( 219 active_color 220 if value is SubTabType.JOIN_BY_ADDRESS 221 else inactive_color 222 ), 223 ) 224 bui.textwidget( 225 edit=self._favorites_text, 226 color=( 227 active_color 228 if value is SubTabType.FAVORITES 229 else inactive_color 230 ), 231 ) 232 233 # Clear anything existing in the old sub-tab. 234 for widget in self._container.get_children(): 235 if widget and widget not in { 236 self._favorites_text, 237 self._join_by_address_text, 238 }: 239 widget.delete() 240 241 if value is SubTabType.JOIN_BY_ADDRESS: 242 self._build_join_by_address_tab(region_width, region_height) 243 244 if value is SubTabType.FAVORITES: 245 self._build_favorites_tab(region_width, region_height) 246 247 # The old manual tab 248 def _build_join_by_address_tab( 249 self, region_width: float, region_height: float 250 ) -> None: 251 c_width = region_width 252 c_height = region_height - 20 253 last_addr = bui.app.config.get('Last Manual Party Connect Address', '') 254 last_port = bui.app.config.get('Last Manual Party Connect Port', 43210) 255 v = c_height - 70 256 v -= 70 257 bui.textwidget( 258 parent=self._container, 259 position=(c_width * 0.5 - 260 - 50, v), 260 color=(0.6, 1.0, 0.6), 261 scale=1.0, 262 size=(0, 0), 263 maxwidth=130, 264 h_align='right', 265 v_align='center', 266 text=bui.Lstr(resource='gatherWindow.' 'manualAddressText'), 267 ) 268 txt = bui.textwidget( 269 parent=self._container, 270 editable=True, 271 description=bui.Lstr(resource='gatherWindow.' 'manualAddressText'), 272 position=(c_width * 0.5 - 240 - 50, v - 30), 273 text=last_addr, 274 autoselect=True, 275 v_align='center', 276 scale=1.0, 277 maxwidth=380, 278 size=(420, 60), 279 ) 280 assert self._join_by_address_text is not None 281 bui.widget(edit=self._join_by_address_text, down_widget=txt) 282 assert self._favorites_text is not None 283 bui.widget(edit=self._favorites_text, down_widget=txt) 284 bui.textwidget( 285 parent=self._container, 286 position=(c_width * 0.5 - 260 + 490, v), 287 color=(0.6, 1.0, 0.6), 288 scale=1.0, 289 size=(0, 0), 290 maxwidth=80, 291 h_align='right', 292 v_align='center', 293 text=bui.Lstr(resource='gatherWindow.' 'portText'), 294 ) 295 txt2 = bui.textwidget( 296 parent=self._container, 297 editable=True, 298 description=bui.Lstr(resource='gatherWindow.' 'portText'), 299 text=str(last_port), 300 autoselect=True, 301 max_chars=5, 302 position=(c_width * 0.5 - 240 + 490, v - 30), 303 v_align='center', 304 scale=1.0, 305 size=(170, 60), 306 ) 307 308 v -= 110 309 310 btn = bui.buttonwidget( 311 parent=self._container, 312 size=(300, 70), 313 label=bui.Lstr(resource='gatherWindow.' 'manualConnectText'), 314 position=(c_width * 0.5 - 300, v), 315 autoselect=True, 316 on_activate_call=bui.Call(self._connect, txt, txt2), 317 ) 318 savebutton = bui.buttonwidget( 319 parent=self._container, 320 size=(300, 70), 321 label=bui.Lstr(resource='gatherWindow.favoritesSaveText'), 322 position=(c_width * 0.5 - 240 + 490 - 200, v), 323 autoselect=True, 324 on_activate_call=bui.Call(self._save_server, txt, txt2), 325 ) 326 bui.widget(edit=btn, right_widget=savebutton) 327 bui.widget(edit=savebutton, left_widget=btn, up_widget=txt2) 328 bui.textwidget(edit=txt, on_return_press_call=btn.activate) 329 bui.textwidget(edit=txt2, on_return_press_call=btn.activate) 330 v -= 45 331 332 self._check_button = bui.textwidget( 333 parent=self._container, 334 size=(250, 60), 335 text=bui.Lstr(resource='gatherWindow.showMyAddressText'), 336 v_align='center', 337 h_align='center', 338 click_activate=True, 339 position=(c_width * 0.5 - 125, v - 30), 340 autoselect=True, 341 color=(0.5, 0.9, 0.5), 342 scale=0.8, 343 selectable=True, 344 on_activate_call=bui.Call( 345 self._on_show_my_address_button_press, 346 v, 347 self._container, 348 c_width, 349 ), 350 glow_type='uniform', 351 ) 352 bui.widget(edit=self._check_button, up_widget=btn) 353 354 # Tab containing saved favorite addresses 355 def _build_favorites_tab( 356 self, region_width: float, region_height: float 357 ) -> None: 358 c_height = region_height - 20 359 v = c_height - 35 - 25 - 30 360 361 assert bui.app.classic is not None 362 uiscale = bui.app.ui_v1.uiscale 363 # self._width = 1240 if uiscale is bui.UIScale.SMALL else 1040 364 self._width = region_width 365 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 366 self._height = ( 367 578 368 if uiscale is bui.UIScale.SMALL 369 else 670 if uiscale is bui.UIScale.MEDIUM else 800 370 ) 371 372 self._scroll_width = self._width - 130 + 2 * x_inset 373 self._scroll_height = self._height - 180 374 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 375 376 c_height = self._scroll_height - 20 377 sub_scroll_height = c_height - 63 378 self._favorites_scroll_width = sub_scroll_width = ( 379 680 if uiscale is bui.UIScale.SMALL else 640 380 ) 381 382 v = c_height - 30 383 384 b_width = 140 if uiscale is bui.UIScale.SMALL else 178 385 b_height = ( 386 107 387 if uiscale is bui.UIScale.SMALL 388 else 142 if uiscale is bui.UIScale.MEDIUM else 190 389 ) 390 b_space_extra = ( 391 0 392 if uiscale is bui.UIScale.SMALL 393 else -2 if uiscale is bui.UIScale.MEDIUM else -5 394 ) 395 396 btnv = ( 397 c_height 398 - ( 399 48 400 if uiscale is bui.UIScale.SMALL 401 else 45 if uiscale is bui.UIScale.MEDIUM else 40 402 ) 403 - b_height 404 ) 405 406 self._favorites_connect_button = btn1 = bui.buttonwidget( 407 parent=self._container, 408 size=(b_width, b_height), 409 position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv), 410 button_type='square', 411 color=(0.6, 0.53, 0.63), 412 textcolor=(0.75, 0.7, 0.8), 413 on_activate_call=self._on_favorites_connect_press, 414 text_scale=1.0 if uiscale is bui.UIScale.SMALL else 1.2, 415 label=bui.Lstr(resource='gatherWindow.manualConnectText'), 416 autoselect=True, 417 ) 418 if uiscale is bui.UIScale.SMALL: 419 bui.widget( 420 edit=btn1, 421 left_widget=bui.get_special_widget('back_button'), 422 ) 423 btnv -= b_height + b_space_extra 424 bui.buttonwidget( 425 parent=self._container, 426 size=(b_width, b_height), 427 position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv), 428 button_type='square', 429 color=(0.6, 0.53, 0.63), 430 textcolor=(0.75, 0.7, 0.8), 431 on_activate_call=self._on_favorites_edit_press, 432 text_scale=1.0 if uiscale is bui.UIScale.SMALL else 1.2, 433 label=bui.Lstr(resource='editText'), 434 autoselect=True, 435 ) 436 btnv -= b_height + b_space_extra 437 bui.buttonwidget( 438 parent=self._container, 439 size=(b_width, b_height), 440 position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv), 441 button_type='square', 442 color=(0.6, 0.53, 0.63), 443 textcolor=(0.75, 0.7, 0.8), 444 on_activate_call=self._on_favorite_delete_press, 445 text_scale=1.0 if uiscale is bui.UIScale.SMALL else 1.2, 446 label=bui.Lstr(resource='deleteText'), 447 autoselect=True, 448 ) 449 450 v -= sub_scroll_height + 23 451 self._scrollwidget = scrlw = bui.scrollwidget( 452 parent=self._container, 453 position=(290 if uiscale is bui.UIScale.SMALL else 225, v), 454 size=(sub_scroll_width, sub_scroll_height), 455 claims_left_right=True, 456 ) 457 bui.widget( 458 edit=self._favorites_connect_button, right_widget=self._scrollwidget 459 ) 460 self._columnwidget = bui.columnwidget( 461 parent=scrlw, 462 left_border=10, 463 border=2, 464 margin=0, 465 claims_left_right=True, 466 ) 467 468 self._no_parties_added_text = bui.textwidget( 469 parent=self._container, 470 size=(0, 0), 471 h_align='center', 472 v_align='center', 473 text='', 474 color=(0.6, 0.6, 0.6), 475 scale=1.2, 476 position=( 477 ( 478 (240 if uiscale is bui.UIScale.SMALL else 225) 479 + sub_scroll_width * 0.5 480 ), 481 v + sub_scroll_height * 0.5, 482 ), 483 glow_type='uniform', 484 ) 485 486 self._favorite_selected = None 487 self._refresh_favorites() 488 489 def _no_favorite_selected_error(self) -> None: 490 bui.screenmessage( 491 bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) 492 ) 493 bui.getsound('error').play() 494 495 def _on_favorites_connect_press(self) -> None: 496 if self._favorite_selected is None: 497 self._no_favorite_selected_error() 498 499 else: 500 config = bui.app.config['Saved Servers'][self._favorite_selected] 501 _HostLookupThread( 502 name=config['addr'], 503 port=config['port'], 504 call=bui.WeakCall(self._host_lookup_result), 505 ).start() 506 507 def _on_favorites_edit_press(self) -> None: 508 if self._favorite_selected is None: 509 self._no_favorite_selected_error() 510 return 511 512 c_width = 600 513 c_height = 310 514 assert bui.app.classic is not None 515 uiscale = bui.app.ui_v1.uiscale 516 self._favorite_edit_window = cnt = bui.containerwidget( 517 scale=( 518 1.8 519 if uiscale is bui.UIScale.SMALL 520 else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.0 521 ), 522 size=(c_width, c_height), 523 transition='in_scale', 524 ) 525 526 bui.textwidget( 527 parent=cnt, 528 size=(0, 0), 529 h_align='center', 530 v_align='center', 531 text=bui.Lstr(resource='editText'), 532 color=(0.6, 1.0, 0.6), 533 maxwidth=c_width * 0.8, 534 position=(c_width * 0.5, c_height - 60), 535 ) 536 537 bui.textwidget( 538 parent=cnt, 539 position=(c_width * 0.2 - 15, c_height - 120), 540 color=(0.6, 1.0, 0.6), 541 scale=1.0, 542 size=(0, 0), 543 maxwidth=60, 544 h_align='right', 545 v_align='center', 546 text=bui.Lstr(resource='nameText'), 547 ) 548 549 self._party_edit_name_text = bui.textwidget( 550 parent=cnt, 551 size=(c_width * 0.7, 40), 552 h_align='left', 553 v_align='center', 554 text=bui.app.config['Saved Servers'][self._favorite_selected][ 555 'name' 556 ], 557 editable=True, 558 description=bui.Lstr(resource='nameText'), 559 position=(c_width * 0.2, c_height - 140), 560 autoselect=True, 561 maxwidth=c_width * 0.6, 562 max_chars=200, 563 ) 564 565 bui.textwidget( 566 parent=cnt, 567 position=(c_width * 0.2 - 15, c_height - 180), 568 color=(0.6, 1.0, 0.6), 569 scale=1.0, 570 size=(0, 0), 571 maxwidth=60, 572 h_align='right', 573 v_align='center', 574 text=bui.Lstr(resource='gatherWindow.' 'manualAddressText'), 575 ) 576 577 self._party_edit_addr_text = bui.textwidget( 578 parent=cnt, 579 size=(c_width * 0.4, 40), 580 h_align='left', 581 v_align='center', 582 text=bui.app.config['Saved Servers'][self._favorite_selected][ 583 'addr' 584 ], 585 editable=True, 586 description=bui.Lstr(resource='gatherWindow.manualAddressText'), 587 position=(c_width * 0.2, c_height - 200), 588 autoselect=True, 589 maxwidth=c_width * 0.35, 590 max_chars=200, 591 ) 592 593 bui.textwidget( 594 parent=cnt, 595 position=(c_width * 0.7 - 10, c_height - 180), 596 color=(0.6, 1.0, 0.6), 597 scale=1.0, 598 size=(0, 0), 599 maxwidth=45, 600 h_align='right', 601 v_align='center', 602 text=bui.Lstr(resource='gatherWindow.' 'portText'), 603 ) 604 605 self._party_edit_port_text = bui.textwidget( 606 parent=cnt, 607 size=(c_width * 0.2, 40), 608 h_align='left', 609 v_align='center', 610 text=str( 611 bui.app.config['Saved Servers'][self._favorite_selected]['port'] 612 ), 613 editable=True, 614 description=bui.Lstr(resource='gatherWindow.portText'), 615 position=(c_width * 0.7, c_height - 200), 616 autoselect=True, 617 maxwidth=c_width * 0.2, 618 max_chars=6, 619 ) 620 cbtn = bui.buttonwidget( 621 parent=cnt, 622 label=bui.Lstr(resource='cancelText'), 623 on_activate_call=bui.Call( 624 lambda c: bui.containerwidget(edit=c, transition='out_scale'), 625 cnt, 626 ), 627 size=(180, 60), 628 position=(30, 30), 629 autoselect=True, 630 ) 631 okb = bui.buttonwidget( 632 parent=cnt, 633 label=bui.Lstr(resource='saveText'), 634 size=(180, 60), 635 position=(c_width - 230, 30), 636 on_activate_call=bui.Call(self._edit_saved_party), 637 autoselect=True, 638 ) 639 bui.widget(edit=cbtn, right_widget=okb) 640 bui.widget(edit=okb, left_widget=cbtn) 641 bui.containerwidget(edit=cnt, cancel_button=cbtn, start_button=okb) 642 643 def _edit_saved_party(self) -> None: 644 server = self._favorite_selected 645 if self._favorite_selected is None: 646 self._no_favorite_selected_error() 647 return 648 if not self._party_edit_name_text or not self._party_edit_addr_text: 649 return 650 new_name_raw = cast( 651 str, bui.textwidget(query=self._party_edit_name_text) 652 ) 653 new_addr_raw = cast( 654 str, bui.textwidget(query=self._party_edit_addr_text) 655 ) 656 new_port_raw = cast( 657 str, bui.textwidget(query=self._party_edit_port_text) 658 ) 659 bui.app.config['Saved Servers'][server]['name'] = new_name_raw 660 bui.app.config['Saved Servers'][server]['addr'] = new_addr_raw 661 try: 662 bui.app.config['Saved Servers'][server]['port'] = int(new_port_raw) 663 except ValueError: 664 # Notify about incorrect port? I'm lazy; simply leave old value. 665 pass 666 bui.app.config.commit() 667 bui.getsound('gunCocking').play() 668 self._refresh_favorites() 669 670 bui.containerwidget( 671 edit=self._favorite_edit_window, transition='out_scale' 672 ) 673 674 def _on_favorite_delete_press(self) -> None: 675 from bauiv1lib import confirm 676 677 if self._favorite_selected is None: 678 self._no_favorite_selected_error() 679 return 680 confirm.ConfirmWindow( 681 bui.Lstr( 682 resource='gameListWindow.deleteConfirmText', 683 subs=[ 684 ( 685 '${LIST}', 686 bui.app.config['Saved Servers'][ 687 self._favorite_selected 688 ]['name'], 689 ) 690 ], 691 ), 692 self._delete_saved_party, 693 450, 694 150, 695 ) 696 697 def _delete_saved_party(self) -> None: 698 if self._favorite_selected is None: 699 self._no_favorite_selected_error() 700 return 701 config = bui.app.config['Saved Servers'] 702 del config[self._favorite_selected] 703 self._favorite_selected = None 704 bui.app.config.commit() 705 bui.getsound('shieldDown').play() 706 self._refresh_favorites() 707 708 def _on_favorite_select(self, server: str) -> None: 709 self._favorite_selected = server 710 711 def _refresh_favorites(self) -> None: 712 assert self._columnwidget is not None 713 for child in self._columnwidget.get_children(): 714 child.delete() 715 t_scale = 1.6 716 717 config = bui.app.config 718 if 'Saved Servers' in config: 719 servers = config['Saved Servers'] 720 721 else: 722 servers = [] 723 724 assert self._favorites_scroll_width is not None 725 assert self._favorites_connect_button is not None 726 727 bui.textwidget( 728 edit=self._no_parties_added_text, 729 text='', 730 ) 731 num_of_fav = 0 732 for i, server in enumerate(servers): 733 txt = bui.textwidget( 734 parent=self._columnwidget, 735 size=(self._favorites_scroll_width / t_scale, 30), 736 selectable=True, 737 color=(1.0, 1, 0.4), 738 always_highlight=True, 739 on_select_call=bui.Call(self._on_favorite_select, server), 740 on_activate_call=self._favorites_connect_button.activate, 741 text=( 742 config['Saved Servers'][server]['name'] 743 if config['Saved Servers'][server]['name'] != '' 744 else config['Saved Servers'][server]['addr'] 745 + ' ' 746 + str(config['Saved Servers'][server]['port']) 747 ), 748 h_align='left', 749 v_align='center', 750 corner_scale=t_scale, 751 maxwidth=(self._favorites_scroll_width / t_scale) * 0.93, 752 ) 753 if i == 0: 754 bui.widget(edit=txt, up_widget=self._favorites_text) 755 self._favorite_selected = server 756 bui.widget( 757 edit=txt, 758 left_widget=self._favorites_connect_button, 759 right_widget=txt, 760 ) 761 num_of_fav = num_of_fav + 1 762 763 # If there's no servers, allow selecting out of the scroll area 764 bui.containerwidget( 765 edit=self._scrollwidget, 766 claims_left_right=bool(servers), 767 claims_up_down=bool(servers), 768 ) 769 assert self._scrollwidget is not None 770 bui.widget( 771 edit=self._scrollwidget, 772 up_widget=self._favorites_text, 773 left_widget=self._favorites_connect_button, 774 ) 775 if num_of_fav == 0: 776 bui.textwidget( 777 edit=self._no_parties_added_text, 778 text=bui.Lstr(resource='gatherWindow.noPartiesAddedText'), 779 ) 780 781 @override 782 def on_deactivate(self) -> None: 783 self._access_check_timer = None 784 785 def _connect( 786 self, textwidget: bui.Widget, port_textwidget: bui.Widget 787 ) -> None: 788 addr = cast(str, bui.textwidget(query=textwidget)) 789 if addr == '': 790 bui.screenmessage( 791 bui.Lstr(resource='internal.invalidAddressErrorText'), 792 color=(1, 0, 0), 793 ) 794 bui.getsound('error').play() 795 return 796 try: 797 port = int(cast(str, bui.textwidget(query=port_textwidget))) 798 except ValueError: 799 port = -1 800 if port > 65535 or port < 0: 801 bui.screenmessage( 802 bui.Lstr(resource='internal.invalidPortErrorText'), 803 color=(1, 0, 0), 804 ) 805 bui.getsound('error').play() 806 return 807 808 _HostLookupThread( 809 name=addr, port=port, call=bui.WeakCall(self._host_lookup_result) 810 ).start() 811 812 def _save_server( 813 self, textwidget: bui.Widget, port_textwidget: bui.Widget 814 ) -> None: 815 addr = cast(str, bui.textwidget(query=textwidget)) 816 if addr == '': 817 bui.screenmessage( 818 bui.Lstr(resource='internal.invalidAddressErrorText'), 819 color=(1, 0, 0), 820 ) 821 bui.getsound('error').play() 822 return 823 try: 824 port = int(cast(str, bui.textwidget(query=port_textwidget))) 825 except ValueError: 826 port = -1 827 if port > 65535 or port < 0: 828 bui.screenmessage( 829 bui.Lstr(resource='internal.invalidPortErrorText'), 830 color=(1, 0, 0), 831 ) 832 bui.getsound('error').play() 833 return 834 config = bui.app.config 835 836 if addr: 837 if not isinstance(config.get('Saved Servers'), dict): 838 config['Saved Servers'] = {} 839 config['Saved Servers'][f'{addr}@{port}'] = { 840 'addr': addr, 841 'port': port, 842 'name': addr, 843 } 844 config.commit() 845 bui.getsound('gunCocking').play() 846 bui.screenmessage( 847 bui.Lstr( 848 resource='addedToFavoritesText', subs=[('${NAME}', addr)] 849 ), 850 color=(0, 1, 0), 851 ) 852 else: 853 bui.screenmessage( 854 bui.Lstr(resource='internal.invalidAddressErrorText'), 855 color=(1, 0, 0), 856 ) 857 bui.getsound('error').play() 858 859 def _host_lookup_result( 860 self, resolved_address: str | None, port: int 861 ) -> None: 862 if resolved_address is None: 863 bui.screenmessage( 864 bui.Lstr(resource='internal.unableToResolveHostText'), 865 color=(1, 0, 0), 866 ) 867 bui.getsound('error').play() 868 else: 869 # Store for later. 870 config = bui.app.config 871 config['Last Manual Party Connect Address'] = resolved_address 872 config['Last Manual Party Connect Port'] = port 873 config.commit() 874 bs.connect_to_party(resolved_address, port=port) 875 876 def _run_addr_fetch(self) -> None: 877 try: 878 # FIXME: Update this to work with IPv6. 879 import socket 880 881 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 882 sock.connect(('8.8.8.8', 80)) 883 val = sock.getsockname()[0] 884 sock.close() 885 bui.pushcall( 886 bui.Call( 887 _safe_set_text, 888 self._checking_state_text, 889 val, 890 ), 891 from_other_thread=True, 892 ) 893 except Exception as exc: 894 from efro.error import is_udp_communication_error 895 896 if is_udp_communication_error(exc): 897 bui.pushcall( 898 bui.Call( 899 _safe_set_text, 900 self._checking_state_text, 901 bui.Lstr(resource='gatherWindow.' 'noConnectionText'), 902 False, 903 ), 904 from_other_thread=True, 905 ) 906 else: 907 bui.pushcall( 908 bui.Call( 909 _safe_set_text, 910 self._checking_state_text, 911 bui.Lstr( 912 resource='gatherWindow.' 'addressFetchErrorText' 913 ), 914 False, 915 ), 916 from_other_thread=True, 917 ) 918 logging.exception('Error in AddrFetchThread.') 919 920 def _on_show_my_address_button_press( 921 self, v2: float, container: bui.Widget | None, c_width: float 922 ) -> None: 923 if not container: 924 return 925 926 tscl = 0.85 927 tspc = 25 928 929 bui.getsound('swish').play() 930 bui.textwidget( 931 parent=container, 932 position=(c_width * 0.5 - 10, v2), 933 color=(0.6, 1.0, 0.6), 934 scale=tscl, 935 size=(0, 0), 936 maxwidth=c_width * 0.45, 937 flatness=1.0, 938 h_align='right', 939 v_align='center', 940 text=bui.Lstr( 941 resource='gatherWindow.' 'manualYourLocalAddressText' 942 ), 943 ) 944 self._checking_state_text = bui.textwidget( 945 parent=container, 946 position=(c_width * 0.5, v2), 947 color=(0.5, 0.5, 0.5), 948 scale=tscl, 949 size=(0, 0), 950 maxwidth=c_width * 0.45, 951 flatness=1.0, 952 h_align='left', 953 v_align='center', 954 text=bui.Lstr(resource='gatherWindow.' 'checkingText'), 955 ) 956 957 Thread(target=self._run_addr_fetch).start() 958 959 v2 -= tspc 960 bui.textwidget( 961 parent=container, 962 position=(c_width * 0.5 - 10, v2), 963 color=(0.6, 1.0, 0.6), 964 scale=tscl, 965 size=(0, 0), 966 maxwidth=c_width * 0.45, 967 flatness=1.0, 968 h_align='right', 969 v_align='center', 970 text=bui.Lstr( 971 resource='gatherWindow.' 'manualYourAddressFromInternetText' 972 ), 973 ) 974 975 t_addr = bui.textwidget( 976 parent=container, 977 position=(c_width * 0.5, v2), 978 color=(0.5, 0.5, 0.5), 979 scale=tscl, 980 size=(0, 0), 981 maxwidth=c_width * 0.45, 982 h_align='left', 983 v_align='center', 984 flatness=1.0, 985 text=bui.Lstr(resource='gatherWindow.' 'checkingText'), 986 ) 987 v2 -= tspc 988 bui.textwidget( 989 parent=container, 990 position=(c_width * 0.5 - 10, v2), 991 color=(0.6, 1.0, 0.6), 992 scale=tscl, 993 size=(0, 0), 994 maxwidth=c_width * 0.45, 995 flatness=1.0, 996 h_align='right', 997 v_align='center', 998 text=bui.Lstr( 999 resource='gatherWindow.' 'manualJoinableFromInternetText' 1000 ), 1001 ) 1002 1003 t_accessible = bui.textwidget( 1004 parent=container, 1005 position=(c_width * 0.5, v2), 1006 color=(0.5, 0.5, 0.5), 1007 scale=tscl, 1008 size=(0, 0), 1009 maxwidth=c_width * 0.45, 1010 flatness=1.0, 1011 h_align='left', 1012 v_align='center', 1013 text=bui.Lstr(resource='gatherWindow.' 'checkingText'), 1014 ) 1015 v2 -= 28 1016 t_accessible_extra = bui.textwidget( 1017 parent=container, 1018 position=(c_width * 0.5, v2), 1019 color=(1, 0.5, 0.2), 1020 scale=0.7, 1021 size=(0, 0), 1022 maxwidth=c_width * 0.9, 1023 flatness=1.0, 1024 h_align='center', 1025 v_align='center', 1026 text='', 1027 ) 1028 1029 self._doing_access_check = False 1030 self._access_check_count = 0 # Cap our refreshes eventually. 1031 self._access_check_timer = bui.AppTimer( 1032 10.0, 1033 bui.WeakCall( 1034 self._access_check_update, 1035 t_addr, 1036 t_accessible, 1037 t_accessible_extra, 1038 ), 1039 repeat=True, 1040 ) 1041 1042 # Kick initial off. 1043 self._access_check_update(t_addr, t_accessible, t_accessible_extra) 1044 if self._check_button: 1045 self._check_button.delete() 1046 1047 def _access_check_update( 1048 self, 1049 t_addr: bui.Widget, 1050 t_accessible: bui.Widget, 1051 t_accessible_extra: bui.Widget, 1052 ) -> None: 1053 assert bui.app.classic is not None 1054 # If we don't have an outstanding query, start one.. 1055 assert self._doing_access_check is not None 1056 assert self._access_check_count is not None 1057 if not self._doing_access_check and self._access_check_count < 100: 1058 self._doing_access_check = True 1059 self._access_check_count += 1 1060 self._t_addr = t_addr 1061 self._t_accessible = t_accessible 1062 self._t_accessible_extra = t_accessible_extra 1063 bui.app.classic.master_server_v1_get( 1064 'bsAccessCheck', 1065 {'b': bui.app.env.engine_build_number}, 1066 callback=bui.WeakCall(self._on_accessible_response), 1067 ) 1068 1069 def _on_accessible_response(self, data: dict[str, Any] | None) -> None: 1070 t_addr = self._t_addr 1071 t_accessible = self._t_accessible 1072 t_accessible_extra = self._t_accessible_extra 1073 self._doing_access_check = False 1074 color_bad = (1, 1, 0) 1075 color_good = (0, 1, 0) 1076 if data is None or 'address' not in data or 'accessible' not in data: 1077 if t_addr: 1078 bui.textwidget( 1079 edit=t_addr, 1080 text=bui.Lstr(resource='gatherWindow.' 'noConnectionText'), 1081 color=color_bad, 1082 ) 1083 if t_accessible: 1084 bui.textwidget( 1085 edit=t_accessible, 1086 text=bui.Lstr(resource='gatherWindow.' 'noConnectionText'), 1087 color=color_bad, 1088 ) 1089 if t_accessible_extra: 1090 bui.textwidget( 1091 edit=t_accessible_extra, text='', color=color_bad 1092 ) 1093 return 1094 if t_addr: 1095 bui.textwidget(edit=t_addr, text=data['address'], color=color_good) 1096 if t_accessible: 1097 if data['accessible']: 1098 bui.textwidget( 1099 edit=t_accessible, 1100 text=bui.Lstr( 1101 resource='gatherWindow.' 'manualJoinableYesText' 1102 ), 1103 color=color_good, 1104 ) 1105 if t_accessible_extra: 1106 bui.textwidget( 1107 edit=t_accessible_extra, text='', color=color_good 1108 ) 1109 else: 1110 bui.textwidget( 1111 edit=t_accessible, 1112 text=bui.Lstr( 1113 resource='gatherWindow.' 1114 'manualJoinableNoWithAsteriskText' 1115 ), 1116 color=color_bad, 1117 ) 1118 if t_accessible_extra: 1119 bui.textwidget( 1120 edit=t_accessible_extra, 1121 text=bui.Lstr( 1122 resource='gatherWindow.' 1123 'manualRouterForwardingText', 1124 subs=[ 1125 ('${PORT}', str(bs.get_game_port())), 1126 ], 1127 ), 1128 color=color_bad, 1129 )
The manual tab in the gather UI
ManualGatherTab(window: bauiv1lib.gather.GatherWindow)
79 def __init__(self, window: GatherWindow) -> None: 80 super().__init__(window) 81 self._check_button: bui.Widget | None = None 82 self._doing_access_check: bool | None = None 83 self._access_check_count: int | None = None 84 self._sub_tab: SubTabType = SubTabType.JOIN_BY_ADDRESS 85 self._t_addr: bui.Widget | None = None 86 self._t_accessible: bui.Widget | None = None 87 self._t_accessible_extra: bui.Widget | None = None 88 self._access_check_timer: bui.AppTimer | None = None 89 self._checking_state_text: bui.Widget | None = None 90 self._container: bui.Widget | None = None 91 self._join_by_address_text: bui.Widget | None = None 92 self._favorites_text: bui.Widget | None = None 93 self._width: float | None = None 94 self._height: float | None = None 95 self._scroll_width: float | None = None 96 self._scroll_height: float | None = None 97 self._favorites_scroll_width: int | None = None 98 self._favorites_connect_button: bui.Widget | None = None 99 self._scrollwidget: bui.Widget | None = None 100 self._columnwidget: bui.Widget | None = None 101 self._favorite_selected: str | None = None 102 self._favorite_edit_window: bui.Widget | None = None 103 self._party_edit_name_text: bui.Widget | None = None 104 self._party_edit_addr_text: bui.Widget | None = None 105 self._party_edit_port_text: bui.Widget | None = None 106 self._no_parties_added_text: bui.Widget | None = None
@override
def
on_activate( self, parent_widget: _bauiv1.Widget, tab_button: _bauiv1.Widget, region_width: float, region_height: float, region_left: float, region_bottom: float) -> _bauiv1.Widget:
108 @override 109 def on_activate( 110 self, 111 parent_widget: bui.Widget, 112 tab_button: bui.Widget, 113 region_width: float, 114 region_height: float, 115 region_left: float, 116 region_bottom: float, 117 ) -> bui.Widget: 118 c_width = region_width 119 c_height = region_height - 20 120 121 self._container = bui.containerwidget( 122 parent=parent_widget, 123 position=( 124 region_left, 125 region_bottom + (region_height - c_height) * 0.5, 126 ), 127 size=(c_width, c_height), 128 background=False, 129 selection_loops_to_parent=True, 130 ) 131 v = c_height - 30 132 self._join_by_address_text = bui.textwidget( 133 parent=self._container, 134 position=(c_width * 0.5 - 245, v - 13), 135 color=(0.6, 1.0, 0.6), 136 scale=1.3, 137 size=(200, 30), 138 maxwidth=250, 139 h_align='center', 140 v_align='center', 141 click_activate=True, 142 selectable=True, 143 autoselect=True, 144 on_activate_call=lambda: self._set_sub_tab( 145 SubTabType.JOIN_BY_ADDRESS, 146 region_width, 147 region_height, 148 playsound=True, 149 ), 150 text=bui.Lstr(resource='gatherWindow.manualJoinSectionText'), 151 glow_type='uniform', 152 ) 153 self._favorites_text = bui.textwidget( 154 parent=self._container, 155 position=(c_width * 0.5 + 45, v - 13), 156 color=(0.6, 1.0, 0.6), 157 scale=1.3, 158 size=(200, 30), 159 maxwidth=250, 160 h_align='center', 161 v_align='center', 162 click_activate=True, 163 selectable=True, 164 autoselect=True, 165 on_activate_call=lambda: self._set_sub_tab( 166 SubTabType.FAVORITES, 167 region_width, 168 region_height, 169 playsound=True, 170 ), 171 text=bui.Lstr(resource='gatherWindow.favoritesText'), 172 glow_type='uniform', 173 ) 174 bui.widget(edit=self._join_by_address_text, up_widget=tab_button) 175 bui.widget( 176 edit=self._favorites_text, 177 left_widget=self._join_by_address_text, 178 up_widget=tab_button, 179 ) 180 bui.widget(edit=tab_button, down_widget=self._favorites_text) 181 bui.widget( 182 edit=self._join_by_address_text, right_widget=self._favorites_text 183 ) 184 self._set_sub_tab(self._sub_tab, region_width, region_height) 185 186 return self._container
Called when the tab becomes the active one.
The tab should create and return a container widget covering the specified region.
@override
def
save_state(self) -> None:
188 @override 189 def save_state(self) -> None: 190 assert bui.app.classic is not None 191 bui.app.ui_v1.window_states[type(self)] = State(sub_tab=self._sub_tab)
Called when the parent window is saving state.
@override
def
restore_state(self) -> None:
193 @override 194 def restore_state(self) -> None: 195 assert bui.app.classic is not None 196 state = bui.app.ui_v1.window_states.get(type(self)) 197 if state is None: 198 state = State() 199 assert isinstance(state, State) 200 self._sub_tab = state.sub_tab
Called when the parent window is restoring state.