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