bastd.ui.confirm
Provides ConfirmWindow base class and commonly used derivatives.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides ConfirmWindow base class and commonly used derivatives.""" 4 5from __future__ import annotations 6 7from typing import TYPE_CHECKING 8 9import ba 10import ba.internal 11 12if TYPE_CHECKING: 13 from typing import Any, Callable 14 15 16class ConfirmWindow: 17 """Window for answering simple yes/no questions.""" 18 19 def __init__( 20 self, 21 text: str | ba.Lstr = 'Are you sure?', 22 action: Callable[[], Any] | None = None, 23 width: float = 360.0, 24 height: float = 100.0, 25 cancel_button: bool = True, 26 cancel_is_selected: bool = False, 27 color: tuple[float, float, float] = (1, 1, 1), 28 text_scale: float = 1.0, 29 ok_text: str | ba.Lstr | None = None, 30 cancel_text: str | ba.Lstr | None = None, 31 origin_widget: ba.Widget | None = None, 32 ): 33 # pylint: disable=too-many-locals 34 if ok_text is None: 35 ok_text = ba.Lstr(resource='okText') 36 if cancel_text is None: 37 cancel_text = ba.Lstr(resource='cancelText') 38 height += 40 39 width = max(width, 360) 40 self._action = action 41 42 # if they provided an origin-widget, scale up from that 43 self._transition_out: str | None 44 scale_origin: tuple[float, float] | None 45 if origin_widget is not None: 46 self._transition_out = 'out_scale' 47 scale_origin = origin_widget.get_screen_space_center() 48 transition = 'in_scale' 49 else: 50 self._transition_out = None 51 scale_origin = None 52 transition = 'in_right' 53 54 uiscale = ba.app.ui.uiscale 55 self.root_widget = ba.containerwidget( 56 size=(width, height), 57 transition=transition, 58 toolbar_visibility='menu_minimal_no_back', 59 parent=ba.internal.get_special_widget('overlay_stack'), 60 scale=( 61 2.1 62 if uiscale is ba.UIScale.SMALL 63 else 1.5 64 if uiscale is ba.UIScale.MEDIUM 65 else 1.0 66 ), 67 scale_origin_stack_offset=scale_origin, 68 ) 69 70 ba.textwidget( 71 parent=self.root_widget, 72 position=(width * 0.5, height - 5 - (height - 75) * 0.5), 73 size=(0, 0), 74 h_align='center', 75 v_align='center', 76 text=text, 77 scale=text_scale, 78 color=color, 79 maxwidth=width * 0.9, 80 max_height=height - 75, 81 ) 82 83 cbtn: ba.Widget | None 84 if cancel_button: 85 cbtn = btn = ba.buttonwidget( 86 parent=self.root_widget, 87 autoselect=True, 88 position=(20, 20), 89 size=(150, 50), 90 label=cancel_text, 91 on_activate_call=self._cancel, 92 ) 93 ba.containerwidget(edit=self.root_widget, cancel_button=btn) 94 ok_button_h = width - 175 95 else: 96 # if they don't want a cancel button, we still want back presses to 97 # be able to dismiss the window; just wire it up to do the ok 98 # button 99 ok_button_h = width * 0.5 - 75 100 cbtn = None 101 btn = ba.buttonwidget( 102 parent=self.root_widget, 103 autoselect=True, 104 position=(ok_button_h, 20), 105 size=(150, 50), 106 label=ok_text, 107 on_activate_call=self._ok, 108 ) 109 110 # if they didn't want a cancel button, we still want to be able to hit 111 # cancel/back/etc to dismiss the window 112 if not cancel_button: 113 ba.containerwidget( 114 edit=self.root_widget, on_cancel_call=btn.activate 115 ) 116 117 ba.containerwidget( 118 edit=self.root_widget, 119 selected_child=( 120 cbtn if cbtn is not None and cancel_is_selected else btn 121 ), 122 start_button=btn, 123 ) 124 125 def _cancel(self) -> None: 126 ba.containerwidget( 127 edit=self.root_widget, 128 transition=( 129 'out_right' 130 if self._transition_out is None 131 else self._transition_out 132 ), 133 ) 134 135 def _ok(self) -> None: 136 if not self.root_widget: 137 return 138 ba.containerwidget( 139 edit=self.root_widget, 140 transition=( 141 'out_left' 142 if self._transition_out is None 143 else self._transition_out 144 ), 145 ) 146 if self._action is not None: 147 self._action() 148 149 150class QuitWindow: 151 """Popup window to confirm quitting.""" 152 153 def __init__( 154 self, 155 swish: bool = False, 156 back: bool = False, 157 origin_widget: ba.Widget | None = None, 158 ): 159 ui = ba.app.ui 160 app = ba.app 161 self._back = back 162 163 # If there's already one of us up somewhere, kill it. 164 if ui.quit_window is not None: 165 ui.quit_window.delete() 166 ui.quit_window = None 167 if swish: 168 ba.playsound(ba.getsound('swish')) 169 quit_resource = ( 170 'quitGameText' if app.platform == 'mac' else 'exitGameText' 171 ) 172 self._root_widget = ui.quit_window = ConfirmWindow( 173 ba.Lstr( 174 resource=quit_resource, 175 subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], 176 ), 177 self._fade_and_quit, 178 origin_widget=origin_widget, 179 ).root_widget 180 181 def _fade_and_quit(self) -> None: 182 ba.internal.fade_screen( 183 False, time=0.2, endcall=lambda: ba.quit(soft=True, back=self._back) 184 ) 185 ba.internal.lock_all_input() 186 187 # Unlock and fade back in shortly.. just in case something goes wrong 188 # (or on android where quit just backs out of our activity and 189 # we may come back) 190 ba.timer(0.3, ba.internal.unlock_all_input, timetype=ba.TimeType.REAL)
class
ConfirmWindow:
17class ConfirmWindow: 18 """Window for answering simple yes/no questions.""" 19 20 def __init__( 21 self, 22 text: str | ba.Lstr = 'Are you sure?', 23 action: Callable[[], Any] | None = None, 24 width: float = 360.0, 25 height: float = 100.0, 26 cancel_button: bool = True, 27 cancel_is_selected: bool = False, 28 color: tuple[float, float, float] = (1, 1, 1), 29 text_scale: float = 1.0, 30 ok_text: str | ba.Lstr | None = None, 31 cancel_text: str | ba.Lstr | None = None, 32 origin_widget: ba.Widget | None = None, 33 ): 34 # pylint: disable=too-many-locals 35 if ok_text is None: 36 ok_text = ba.Lstr(resource='okText') 37 if cancel_text is None: 38 cancel_text = ba.Lstr(resource='cancelText') 39 height += 40 40 width = max(width, 360) 41 self._action = action 42 43 # if they provided an origin-widget, scale up from that 44 self._transition_out: str | None 45 scale_origin: tuple[float, float] | None 46 if origin_widget is not None: 47 self._transition_out = 'out_scale' 48 scale_origin = origin_widget.get_screen_space_center() 49 transition = 'in_scale' 50 else: 51 self._transition_out = None 52 scale_origin = None 53 transition = 'in_right' 54 55 uiscale = ba.app.ui.uiscale 56 self.root_widget = ba.containerwidget( 57 size=(width, height), 58 transition=transition, 59 toolbar_visibility='menu_minimal_no_back', 60 parent=ba.internal.get_special_widget('overlay_stack'), 61 scale=( 62 2.1 63 if uiscale is ba.UIScale.SMALL 64 else 1.5 65 if uiscale is ba.UIScale.MEDIUM 66 else 1.0 67 ), 68 scale_origin_stack_offset=scale_origin, 69 ) 70 71 ba.textwidget( 72 parent=self.root_widget, 73 position=(width * 0.5, height - 5 - (height - 75) * 0.5), 74 size=(0, 0), 75 h_align='center', 76 v_align='center', 77 text=text, 78 scale=text_scale, 79 color=color, 80 maxwidth=width * 0.9, 81 max_height=height - 75, 82 ) 83 84 cbtn: ba.Widget | None 85 if cancel_button: 86 cbtn = btn = ba.buttonwidget( 87 parent=self.root_widget, 88 autoselect=True, 89 position=(20, 20), 90 size=(150, 50), 91 label=cancel_text, 92 on_activate_call=self._cancel, 93 ) 94 ba.containerwidget(edit=self.root_widget, cancel_button=btn) 95 ok_button_h = width - 175 96 else: 97 # if they don't want a cancel button, we still want back presses to 98 # be able to dismiss the window; just wire it up to do the ok 99 # button 100 ok_button_h = width * 0.5 - 75 101 cbtn = None 102 btn = ba.buttonwidget( 103 parent=self.root_widget, 104 autoselect=True, 105 position=(ok_button_h, 20), 106 size=(150, 50), 107 label=ok_text, 108 on_activate_call=self._ok, 109 ) 110 111 # if they didn't want a cancel button, we still want to be able to hit 112 # cancel/back/etc to dismiss the window 113 if not cancel_button: 114 ba.containerwidget( 115 edit=self.root_widget, on_cancel_call=btn.activate 116 ) 117 118 ba.containerwidget( 119 edit=self.root_widget, 120 selected_child=( 121 cbtn if cbtn is not None and cancel_is_selected else btn 122 ), 123 start_button=btn, 124 ) 125 126 def _cancel(self) -> None: 127 ba.containerwidget( 128 edit=self.root_widget, 129 transition=( 130 'out_right' 131 if self._transition_out is None 132 else self._transition_out 133 ), 134 ) 135 136 def _ok(self) -> None: 137 if not self.root_widget: 138 return 139 ba.containerwidget( 140 edit=self.root_widget, 141 transition=( 142 'out_left' 143 if self._transition_out is None 144 else self._transition_out 145 ), 146 ) 147 if self._action is not None: 148 self._action()
Window for answering simple yes/no questions.
ConfirmWindow( text: str | ba._language.Lstr = 'Are you sure?', action: Optional[Callable[[], Any]] = None, width: float = 360.0, height: float = 100.0, cancel_button: bool = True, cancel_is_selected: bool = False, color: tuple[float, float, float] = (1, 1, 1), text_scale: float = 1.0, ok_text: str | ba._language.Lstr | None = None, cancel_text: str | ba._language.Lstr | None = None, origin_widget: _ba.Widget | None = None)
20 def __init__( 21 self, 22 text: str | ba.Lstr = 'Are you sure?', 23 action: Callable[[], Any] | None = None, 24 width: float = 360.0, 25 height: float = 100.0, 26 cancel_button: bool = True, 27 cancel_is_selected: bool = False, 28 color: tuple[float, float, float] = (1, 1, 1), 29 text_scale: float = 1.0, 30 ok_text: str | ba.Lstr | None = None, 31 cancel_text: str | ba.Lstr | None = None, 32 origin_widget: ba.Widget | None = None, 33 ): 34 # pylint: disable=too-many-locals 35 if ok_text is None: 36 ok_text = ba.Lstr(resource='okText') 37 if cancel_text is None: 38 cancel_text = ba.Lstr(resource='cancelText') 39 height += 40 40 width = max(width, 360) 41 self._action = action 42 43 # if they provided an origin-widget, scale up from that 44 self._transition_out: str | None 45 scale_origin: tuple[float, float] | None 46 if origin_widget is not None: 47 self._transition_out = 'out_scale' 48 scale_origin = origin_widget.get_screen_space_center() 49 transition = 'in_scale' 50 else: 51 self._transition_out = None 52 scale_origin = None 53 transition = 'in_right' 54 55 uiscale = ba.app.ui.uiscale 56 self.root_widget = ba.containerwidget( 57 size=(width, height), 58 transition=transition, 59 toolbar_visibility='menu_minimal_no_back', 60 parent=ba.internal.get_special_widget('overlay_stack'), 61 scale=( 62 2.1 63 if uiscale is ba.UIScale.SMALL 64 else 1.5 65 if uiscale is ba.UIScale.MEDIUM 66 else 1.0 67 ), 68 scale_origin_stack_offset=scale_origin, 69 ) 70 71 ba.textwidget( 72 parent=self.root_widget, 73 position=(width * 0.5, height - 5 - (height - 75) * 0.5), 74 size=(0, 0), 75 h_align='center', 76 v_align='center', 77 text=text, 78 scale=text_scale, 79 color=color, 80 maxwidth=width * 0.9, 81 max_height=height - 75, 82 ) 83 84 cbtn: ba.Widget | None 85 if cancel_button: 86 cbtn = btn = ba.buttonwidget( 87 parent=self.root_widget, 88 autoselect=True, 89 position=(20, 20), 90 size=(150, 50), 91 label=cancel_text, 92 on_activate_call=self._cancel, 93 ) 94 ba.containerwidget(edit=self.root_widget, cancel_button=btn) 95 ok_button_h = width - 175 96 else: 97 # if they don't want a cancel button, we still want back presses to 98 # be able to dismiss the window; just wire it up to do the ok 99 # button 100 ok_button_h = width * 0.5 - 75 101 cbtn = None 102 btn = ba.buttonwidget( 103 parent=self.root_widget, 104 autoselect=True, 105 position=(ok_button_h, 20), 106 size=(150, 50), 107 label=ok_text, 108 on_activate_call=self._ok, 109 ) 110 111 # if they didn't want a cancel button, we still want to be able to hit 112 # cancel/back/etc to dismiss the window 113 if not cancel_button: 114 ba.containerwidget( 115 edit=self.root_widget, on_cancel_call=btn.activate 116 ) 117 118 ba.containerwidget( 119 edit=self.root_widget, 120 selected_child=( 121 cbtn if cbtn is not None and cancel_is_selected else btn 122 ), 123 start_button=btn, 124 )
class
QuitWindow:
151class QuitWindow: 152 """Popup window to confirm quitting.""" 153 154 def __init__( 155 self, 156 swish: bool = False, 157 back: bool = False, 158 origin_widget: ba.Widget | None = None, 159 ): 160 ui = ba.app.ui 161 app = ba.app 162 self._back = back 163 164 # If there's already one of us up somewhere, kill it. 165 if ui.quit_window is not None: 166 ui.quit_window.delete() 167 ui.quit_window = None 168 if swish: 169 ba.playsound(ba.getsound('swish')) 170 quit_resource = ( 171 'quitGameText' if app.platform == 'mac' else 'exitGameText' 172 ) 173 self._root_widget = ui.quit_window = ConfirmWindow( 174 ba.Lstr( 175 resource=quit_resource, 176 subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], 177 ), 178 self._fade_and_quit, 179 origin_widget=origin_widget, 180 ).root_widget 181 182 def _fade_and_quit(self) -> None: 183 ba.internal.fade_screen( 184 False, time=0.2, endcall=lambda: ba.quit(soft=True, back=self._back) 185 ) 186 ba.internal.lock_all_input() 187 188 # Unlock and fade back in shortly.. just in case something goes wrong 189 # (or on android where quit just backs out of our activity and 190 # we may come back) 191 ba.timer(0.3, ba.internal.unlock_all_input, timetype=ba.TimeType.REAL)
Popup window to confirm quitting.
QuitWindow( swish: bool = False, back: bool = False, origin_widget: _ba.Widget | None = None)
154 def __init__( 155 self, 156 swish: bool = False, 157 back: bool = False, 158 origin_widget: ba.Widget | None = None, 159 ): 160 ui = ba.app.ui 161 app = ba.app 162 self._back = back 163 164 # If there's already one of us up somewhere, kill it. 165 if ui.quit_window is not None: 166 ui.quit_window.delete() 167 ui.quit_window = None 168 if swish: 169 ba.playsound(ba.getsound('swish')) 170 quit_resource = ( 171 'quitGameText' if app.platform == 'mac' else 'exitGameText' 172 ) 173 self._root_widget = ui.quit_window = ConfirmWindow( 174 ba.Lstr( 175 resource=quit_resource, 176 subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], 177 ), 178 self._fade_and_quit, 179 origin_widget=origin_widget, 180 ).root_widget