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