# Released under the MIT License. See LICENSE for details. # """Useful starting points for new classes and whatnot.""" from __future__ import annotations import random from typing import override import bauiv1 as bui def show_template_main_window() -> None: """Bust out a template-main-window.""" # Pop up an auxiliary window wherever we are in the nav stack. bui.app.ui_v1.auxiliary_window_activate( win_type=TemplateMainWindow, win_create_call=lambda: TemplateMainWindow( dummy_data=random.randrange(100, 1000) ), ) class TemplateMainWindow(bui.MainWindow): """An example of a well-behaved main-window.""" def __init__( self, dummy_data: int, *, transition: str | None = 'in_right', origin_widget: bui.Widget | None = None, auxiliary_style: bool = True, ): ui = bui.app.ui_v1 # A simple number standing in for actual data (to show how we'd # save/restore actual data). self._dummy_data = dummy_data # We want to display differently whether we're an auxiliary # window or not, but unfortunately that value is not yet # available until we're added to the main-window-stack so it # must be explicitly passed in. self._auxiliary_style = auxiliary_style # Calc scale and size for our backing window. For medium & large # ui-scale we aim for a window small enough to always be fully # visible on-screen and for small mode we aim for a window big # enough that we never see the window edges; only the window # texture covering the whole screen. uiscale = ui.uiscale self._width = 1400 if uiscale is bui.UIScale.SMALL else 750 self._height = 1200 if uiscale is bui.UIScale.SMALL else 500 scale = ( 1.5 if uiscale is bui.UIScale.SMALL else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0 ) # Do some fancy math to calculate our visible area; this will be # limited by the screen size in small mode and our backing size # otherwise. screensize = bui.get_virtual_screen_size() self._vis_width = min(self._width - 100, screensize[0] / scale) self._vis_height = min(self._height - 100, screensize[1] / scale) self._vis_top = 0.5 * self._height + 0.5 * self._vis_height self._vis_left = 0.5 * self._width - 0.5 * self._vis_width # Nudge our vis area up a bit when we can see the full backing # (visual fudge factor). if uiscale is not bui.UIScale.SMALL: self._vis_top += 12.0 super().__init__( root_widget=bui.containerwidget( size=(self._width, self._height), toolbar_visibility='menu_full', toolbar_cancel_button_style=( 'close' if auxiliary_style else 'back' ), scale=scale, ), transition=transition, origin_widget=origin_widget, # We respond to screen size changes only at small ui-scale; # in other cases we assume our window remains fully visible # always (flip to windowed mode and resize the app window to # confirm this). refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, ) # Title. bui.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._vis_top - 20), size=(0, 0), text=f'Template{self._dummy_data}', color=ui.title_color, scale=0.9 if uiscale is bui.UIScale.SMALL else 1.0, # Make sure we avoid overlapping meters in small mode/etc. maxwidth=(130 if uiscale is bui.UIScale.SMALL else 200), h_align='center', v_align='center', ) # For small UI-scale we use the system back/close button; # otherwise we make our own. if uiscale is bui.UIScale.SMALL: bui.containerwidget( edit=self._root_widget, on_cancel_call=self.main_window_back ) else: btn = bui.buttonwidget( parent=self._root_widget, id=f'{self.main_window_id_prefix}|close', scale=0.8, position=(self._vis_left - 15, self._vis_top - 30), size=(50, 50) if auxiliary_style else (60, 55), extra_touch_border_scale=2.0, button_type=None if auxiliary_style else 'backSmall', on_activate_call=self.main_window_back, autoselect=True, label=bui.charstr( bui.SpecialChar.CLOSE if auxiliary_style else bui.SpecialChar.BACK ), ) bui.containerwidget(edit=self._root_widget, cancel_button=btn) # Show our vis-area bounds (for debugging). if bool(True): # Skip top-left since its always overlapping back/close # buttons. if bool(False): bui.textwidget( parent=self._root_widget, position=(self._vis_left, self._vis_top), size=(0, 0), color=(1, 1, 1, 0.5), scale=0.5, text='TL', h_align='left', v_align='top', ) bui.textwidget( parent=self._root_widget, position=(self._vis_left + self._vis_width, self._vis_top), size=(0, 0), color=(1, 1, 1, 0.5), scale=0.5, text='TR', h_align='right', v_align='top', ) bui.textwidget( parent=self._root_widget, position=(self._vis_left, self._vis_top - self._vis_height), size=(0, 0), color=(1, 1, 1, 0.5), scale=0.5, text='BL', h_align='left', v_align='bottom', ) bui.textwidget( parent=self._root_widget, position=( self._vis_left + self._vis_width, self._vis_top - self._vis_height, ), size=(0, 0), scale=0.5, color=(1, 1, 1, 0.5), text='BR', h_align='right', v_align='bottom', ) # Description. bui.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._vis_top - 100), size=(0, 0), scale=0.6, text=( f'Use this class as reference for making' f' a well-behaved MainWindow class or for\n' 'navigating between MainWindows. It lives at' f' {self.__module__}.{type(self).__qualname__}.\n' f'vis-size=({round(self._vis_width)},' f' {round(self._vis_height)})' ), h_align='center', v_align='center', ) # Make a few buttons to navigate to other MainWindows (simply # our same class with random different dummy values). button_width = 300 for i in range(3): child_dummy_data = self._dummy_data + (i + 1) * 17 self._player_profiles_button = btn = bui.buttonwidget( parent=self._root_widget, id=f'{self.main_window_id_prefix}|button{i + 1}', position=( self._width * 0.5 - button_width * 0.5, self._vis_top - 230 - i * 80, ), autoselect=True, size=(button_width, 60), label=f'Template{child_dummy_data}', on_activate_call=bui.WeakCallStrict( self._child_press, child_dummy_data ), ) def _child_press(self, dummy_data: int) -> None: # Navigate to a new one of us. self.main_window_replace( lambda: TemplateMainWindow( auxiliary_style=False, dummy_data=dummy_data ) ) @override def get_main_window_state(self) -> bui.MainWindowState: # Support recreating our window for back/refresh purposes. cls = type(self) # IMPORTANT - Pull values from self HERE; if we do it in the # lambda below then the state will keep self alive which will # lead to 'ui-not-getting-cleaned-up' warnings and memory leaks. dummy_data = self._dummy_data auxiliary_style = self._auxiliary_style return bui.BasicMainWindowState( create_call=lambda transition, origin_widget: cls( transition=transition, origin_widget=origin_widget, dummy_data=dummy_data, auxiliary_style=auxiliary_style, ), ) @override def main_window_should_preserve_selection(self) -> bool: # If we return True here, the app will reselect the last # selected widget when creating a new one of our windows. We # just need to make sure all of our selectable widgets have # unique ids starting with `self.main_window_id_prefix`. return True @override def get_main_window_shared_state_id(self) -> str | None: # Here we return a unique id based on what we're displaying. # This means each level will remember its selected button. If we # remove this function override we get the default behavior # where this state is shared for all instances of our class # (thus selecting the second button will go to a new window with # the second button already selected). return f'template{self._dummy_data}'