bastd.ui.characterpicker

Provides a picker for characters.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides a picker for characters."""
  4
  5from __future__ import annotations
  6
  7import math
  8from typing import TYPE_CHECKING
  9
 10import ba
 11import ba.internal
 12from bastd.ui import popup
 13
 14if TYPE_CHECKING:
 15    from typing import Any, Sequence
 16
 17
 18class CharacterPicker(popup.PopupWindow):
 19    """Popup window for selecting characters."""
 20
 21    def __init__(
 22        self,
 23        parent: ba.Widget,
 24        position: tuple[float, float] = (0.0, 0.0),
 25        delegate: Any = None,
 26        scale: float | None = None,
 27        offset: tuple[float, float] = (0.0, 0.0),
 28        tint_color: Sequence[float] = (1.0, 1.0, 1.0),
 29        tint2_color: Sequence[float] = (1.0, 1.0, 1.0),
 30        selected_character: str | None = None,
 31    ):
 32        # pylint: disable=too-many-locals
 33        from bastd.actor import spazappearance
 34
 35        del parent  # unused here
 36        uiscale = ba.app.ui.uiscale
 37        if scale is None:
 38            scale = (
 39                1.85
 40                if uiscale is ba.UIScale.SMALL
 41                else 1.65
 42                if uiscale is ba.UIScale.MEDIUM
 43                else 1.23
 44            )
 45
 46        self._delegate = delegate
 47        self._transitioning_out = False
 48
 49        # make a list of spaz icons
 50        self._spazzes = spazappearance.get_appearances()
 51        self._spazzes.sort()
 52        self._icon_textures = [
 53            ba.gettexture(ba.app.spaz_appearances[s].icon_texture)
 54            for s in self._spazzes
 55        ]
 56        self._icon_tint_textures = [
 57            ba.gettexture(ba.app.spaz_appearances[s].icon_mask_texture)
 58            for s in self._spazzes
 59        ]
 60
 61        count = len(self._spazzes)
 62
 63        columns = 3
 64        rows = int(math.ceil(float(count) / columns))
 65
 66        button_width = 100
 67        button_height = 100
 68        button_buffer_h = 10
 69        button_buffer_v = 15
 70
 71        self._width = 10 + columns * (button_width + 2 * button_buffer_h) * (
 72            1.0 / 0.95
 73        ) * (1.0 / 0.8)
 74        self._height = self._width * (
 75            0.8 if uiscale is ba.UIScale.SMALL else 1.06
 76        )
 77
 78        self._scroll_width = self._width * 0.8
 79        self._scroll_height = self._height * 0.8
 80        self._scroll_position = (
 81            (self._width - self._scroll_width) * 0.5,
 82            (self._height - self._scroll_height) * 0.5,
 83        )
 84
 85        # creates our _root_widget
 86        popup.PopupWindow.__init__(
 87            self,
 88            position=position,
 89            size=(self._width, self._height),
 90            scale=scale,
 91            bg_color=(0.5, 0.5, 0.5),
 92            offset=offset,
 93            focus_position=self._scroll_position,
 94            focus_size=(self._scroll_width, self._scroll_height),
 95        )
 96
 97        self._scrollwidget = ba.scrollwidget(
 98            parent=self.root_widget,
 99            size=(self._scroll_width, self._scroll_height),
100            color=(0.55, 0.55, 0.55),
101            highlight=False,
102            position=self._scroll_position,
103        )
104        ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
105
106        self._sub_width = self._scroll_width * 0.95
107        self._sub_height = (
108            5 + rows * (button_height + 2 * button_buffer_v) + 100
109        )
110        self._subcontainer = ba.containerwidget(
111            parent=self._scrollwidget,
112            size=(self._sub_width, self._sub_height),
113            background=False,
114        )
115        index = 0
116        mask_texture = ba.gettexture('characterIconMask')
117        for y in range(rows):
118            for x in range(columns):
119                pos = (
120                    x * (button_width + 2 * button_buffer_h) + button_buffer_h,
121                    self._sub_height
122                    - (y + 1) * (button_height + 2 * button_buffer_v)
123                    + 12,
124                )
125                btn = ba.buttonwidget(
126                    parent=self._subcontainer,
127                    button_type='square',
128                    size=(button_width, button_height),
129                    autoselect=True,
130                    texture=self._icon_textures[index],
131                    tint_texture=self._icon_tint_textures[index],
132                    mask_texture=mask_texture,
133                    label='',
134                    color=(1, 1, 1),
135                    tint_color=tint_color,
136                    tint2_color=tint2_color,
137                    on_activate_call=ba.Call(
138                        self._select_character, self._spazzes[index]
139                    ),
140                    position=pos,
141                )
142                ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
143                if self._spazzes[index] == selected_character:
144                    ba.containerwidget(
145                        edit=self._subcontainer,
146                        selected_child=btn,
147                        visible_child=btn,
148                    )
149                name = ba.Lstr(
150                    translate=('characterNames', self._spazzes[index])
151                )
152                ba.textwidget(
153                    parent=self._subcontainer,
154                    text=name,
155                    position=(pos[0] + button_width * 0.5, pos[1] - 12),
156                    size=(0, 0),
157                    scale=0.5,
158                    maxwidth=button_width,
159                    draw_controller=btn,
160                    h_align='center',
161                    v_align='center',
162                    color=(0.8, 0.8, 0.8, 0.8),
163                )
164                index += 1
165
166                if index >= count:
167                    break
168            if index >= count:
169                break
170        self._get_more_characters_button = btn = ba.buttonwidget(
171            parent=self._subcontainer,
172            size=(self._sub_width * 0.8, 60),
173            position=(self._sub_width * 0.1, 30),
174            label=ba.Lstr(resource='editProfileWindow.getMoreCharactersText'),
175            on_activate_call=self._on_store_press,
176            color=(0.6, 0.6, 0.6),
177            textcolor=(0.8, 0.8, 0.8),
178            autoselect=True,
179        )
180        ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30)
181
182    def _on_store_press(self) -> None:
183        from bastd.ui.account import show_sign_in_prompt
184        from bastd.ui.store.browser import StoreBrowserWindow
185
186        if ba.internal.get_v1_account_state() != 'signed_in':
187            show_sign_in_prompt()
188            return
189        self._transition_out()
190        StoreBrowserWindow(
191            modal=True,
192            show_tab=StoreBrowserWindow.TabID.CHARACTERS,
193            origin_widget=self._get_more_characters_button,
194        )
195
196    def _select_character(self, character: str) -> None:
197        if self._delegate is not None:
198            self._delegate.on_character_picker_pick(character)
199        self._transition_out()
200
201    def _transition_out(self) -> None:
202        if not self._transitioning_out:
203            self._transitioning_out = True
204            ba.containerwidget(edit=self.root_widget, transition='out_scale')
205
206    def on_popup_cancel(self) -> None:
207        ba.playsound(ba.getsound('swish'))
208        self._transition_out()
class CharacterPicker(bastd.ui.popup.PopupWindow):
 19class CharacterPicker(popup.PopupWindow):
 20    """Popup window for selecting characters."""
 21
 22    def __init__(
 23        self,
 24        parent: ba.Widget,
 25        position: tuple[float, float] = (0.0, 0.0),
 26        delegate: Any = None,
 27        scale: float | None = None,
 28        offset: tuple[float, float] = (0.0, 0.0),
 29        tint_color: Sequence[float] = (1.0, 1.0, 1.0),
 30        tint2_color: Sequence[float] = (1.0, 1.0, 1.0),
 31        selected_character: str | None = None,
 32    ):
 33        # pylint: disable=too-many-locals
 34        from bastd.actor import spazappearance
 35
 36        del parent  # unused here
 37        uiscale = ba.app.ui.uiscale
 38        if scale is None:
 39            scale = (
 40                1.85
 41                if uiscale is ba.UIScale.SMALL
 42                else 1.65
 43                if uiscale is ba.UIScale.MEDIUM
 44                else 1.23
 45            )
 46
 47        self._delegate = delegate
 48        self._transitioning_out = False
 49
 50        # make a list of spaz icons
 51        self._spazzes = spazappearance.get_appearances()
 52        self._spazzes.sort()
 53        self._icon_textures = [
 54            ba.gettexture(ba.app.spaz_appearances[s].icon_texture)
 55            for s in self._spazzes
 56        ]
 57        self._icon_tint_textures = [
 58            ba.gettexture(ba.app.spaz_appearances[s].icon_mask_texture)
 59            for s in self._spazzes
 60        ]
 61
 62        count = len(self._spazzes)
 63
 64        columns = 3
 65        rows = int(math.ceil(float(count) / columns))
 66
 67        button_width = 100
 68        button_height = 100
 69        button_buffer_h = 10
 70        button_buffer_v = 15
 71
 72        self._width = 10 + columns * (button_width + 2 * button_buffer_h) * (
 73            1.0 / 0.95
 74        ) * (1.0 / 0.8)
 75        self._height = self._width * (
 76            0.8 if uiscale is ba.UIScale.SMALL else 1.06
 77        )
 78
 79        self._scroll_width = self._width * 0.8
 80        self._scroll_height = self._height * 0.8
 81        self._scroll_position = (
 82            (self._width - self._scroll_width) * 0.5,
 83            (self._height - self._scroll_height) * 0.5,
 84        )
 85
 86        # creates our _root_widget
 87        popup.PopupWindow.__init__(
 88            self,
 89            position=position,
 90            size=(self._width, self._height),
 91            scale=scale,
 92            bg_color=(0.5, 0.5, 0.5),
 93            offset=offset,
 94            focus_position=self._scroll_position,
 95            focus_size=(self._scroll_width, self._scroll_height),
 96        )
 97
 98        self._scrollwidget = ba.scrollwidget(
 99            parent=self.root_widget,
100            size=(self._scroll_width, self._scroll_height),
101            color=(0.55, 0.55, 0.55),
102            highlight=False,
103            position=self._scroll_position,
104        )
105        ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
106
107        self._sub_width = self._scroll_width * 0.95
108        self._sub_height = (
109            5 + rows * (button_height + 2 * button_buffer_v) + 100
110        )
111        self._subcontainer = ba.containerwidget(
112            parent=self._scrollwidget,
113            size=(self._sub_width, self._sub_height),
114            background=False,
115        )
116        index = 0
117        mask_texture = ba.gettexture('characterIconMask')
118        for y in range(rows):
119            for x in range(columns):
120                pos = (
121                    x * (button_width + 2 * button_buffer_h) + button_buffer_h,
122                    self._sub_height
123                    - (y + 1) * (button_height + 2 * button_buffer_v)
124                    + 12,
125                )
126                btn = ba.buttonwidget(
127                    parent=self._subcontainer,
128                    button_type='square',
129                    size=(button_width, button_height),
130                    autoselect=True,
131                    texture=self._icon_textures[index],
132                    tint_texture=self._icon_tint_textures[index],
133                    mask_texture=mask_texture,
134                    label='',
135                    color=(1, 1, 1),
136                    tint_color=tint_color,
137                    tint2_color=tint2_color,
138                    on_activate_call=ba.Call(
139                        self._select_character, self._spazzes[index]
140                    ),
141                    position=pos,
142                )
143                ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
144                if self._spazzes[index] == selected_character:
145                    ba.containerwidget(
146                        edit=self._subcontainer,
147                        selected_child=btn,
148                        visible_child=btn,
149                    )
150                name = ba.Lstr(
151                    translate=('characterNames', self._spazzes[index])
152                )
153                ba.textwidget(
154                    parent=self._subcontainer,
155                    text=name,
156                    position=(pos[0] + button_width * 0.5, pos[1] - 12),
157                    size=(0, 0),
158                    scale=0.5,
159                    maxwidth=button_width,
160                    draw_controller=btn,
161                    h_align='center',
162                    v_align='center',
163                    color=(0.8, 0.8, 0.8, 0.8),
164                )
165                index += 1
166
167                if index >= count:
168                    break
169            if index >= count:
170                break
171        self._get_more_characters_button = btn = ba.buttonwidget(
172            parent=self._subcontainer,
173            size=(self._sub_width * 0.8, 60),
174            position=(self._sub_width * 0.1, 30),
175            label=ba.Lstr(resource='editProfileWindow.getMoreCharactersText'),
176            on_activate_call=self._on_store_press,
177            color=(0.6, 0.6, 0.6),
178            textcolor=(0.8, 0.8, 0.8),
179            autoselect=True,
180        )
181        ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30)
182
183    def _on_store_press(self) -> None:
184        from bastd.ui.account import show_sign_in_prompt
185        from bastd.ui.store.browser import StoreBrowserWindow
186
187        if ba.internal.get_v1_account_state() != 'signed_in':
188            show_sign_in_prompt()
189            return
190        self._transition_out()
191        StoreBrowserWindow(
192            modal=True,
193            show_tab=StoreBrowserWindow.TabID.CHARACTERS,
194            origin_widget=self._get_more_characters_button,
195        )
196
197    def _select_character(self, character: str) -> None:
198        if self._delegate is not None:
199            self._delegate.on_character_picker_pick(character)
200        self._transition_out()
201
202    def _transition_out(self) -> None:
203        if not self._transitioning_out:
204            self._transitioning_out = True
205            ba.containerwidget(edit=self.root_widget, transition='out_scale')
206
207    def on_popup_cancel(self) -> None:
208        ba.playsound(ba.getsound('swish'))
209        self._transition_out()

Popup window for selecting characters.

CharacterPicker( parent: _ba.Widget, position: tuple[float, float] = (0.0, 0.0), delegate: Any = None, scale: float | None = None, offset: tuple[float, float] = (0.0, 0.0), tint_color: Sequence[float] = (1.0, 1.0, 1.0), tint2_color: Sequence[float] = (1.0, 1.0, 1.0), selected_character: str | None = None)
 22    def __init__(
 23        self,
 24        parent: ba.Widget,
 25        position: tuple[float, float] = (0.0, 0.0),
 26        delegate: Any = None,
 27        scale: float | None = None,
 28        offset: tuple[float, float] = (0.0, 0.0),
 29        tint_color: Sequence[float] = (1.0, 1.0, 1.0),
 30        tint2_color: Sequence[float] = (1.0, 1.0, 1.0),
 31        selected_character: str | None = None,
 32    ):
 33        # pylint: disable=too-many-locals
 34        from bastd.actor import spazappearance
 35
 36        del parent  # unused here
 37        uiscale = ba.app.ui.uiscale
 38        if scale is None:
 39            scale = (
 40                1.85
 41                if uiscale is ba.UIScale.SMALL
 42                else 1.65
 43                if uiscale is ba.UIScale.MEDIUM
 44                else 1.23
 45            )
 46
 47        self._delegate = delegate
 48        self._transitioning_out = False
 49
 50        # make a list of spaz icons
 51        self._spazzes = spazappearance.get_appearances()
 52        self._spazzes.sort()
 53        self._icon_textures = [
 54            ba.gettexture(ba.app.spaz_appearances[s].icon_texture)
 55            for s in self._spazzes
 56        ]
 57        self._icon_tint_textures = [
 58            ba.gettexture(ba.app.spaz_appearances[s].icon_mask_texture)
 59            for s in self._spazzes
 60        ]
 61
 62        count = len(self._spazzes)
 63
 64        columns = 3
 65        rows = int(math.ceil(float(count) / columns))
 66
 67        button_width = 100
 68        button_height = 100
 69        button_buffer_h = 10
 70        button_buffer_v = 15
 71
 72        self._width = 10 + columns * (button_width + 2 * button_buffer_h) * (
 73            1.0 / 0.95
 74        ) * (1.0 / 0.8)
 75        self._height = self._width * (
 76            0.8 if uiscale is ba.UIScale.SMALL else 1.06
 77        )
 78
 79        self._scroll_width = self._width * 0.8
 80        self._scroll_height = self._height * 0.8
 81        self._scroll_position = (
 82            (self._width - self._scroll_width) * 0.5,
 83            (self._height - self._scroll_height) * 0.5,
 84        )
 85
 86        # creates our _root_widget
 87        popup.PopupWindow.__init__(
 88            self,
 89            position=position,
 90            size=(self._width, self._height),
 91            scale=scale,
 92            bg_color=(0.5, 0.5, 0.5),
 93            offset=offset,
 94            focus_position=self._scroll_position,
 95            focus_size=(self._scroll_width, self._scroll_height),
 96        )
 97
 98        self._scrollwidget = ba.scrollwidget(
 99            parent=self.root_widget,
100            size=(self._scroll_width, self._scroll_height),
101            color=(0.55, 0.55, 0.55),
102            highlight=False,
103            position=self._scroll_position,
104        )
105        ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
106
107        self._sub_width = self._scroll_width * 0.95
108        self._sub_height = (
109            5 + rows * (button_height + 2 * button_buffer_v) + 100
110        )
111        self._subcontainer = ba.containerwidget(
112            parent=self._scrollwidget,
113            size=(self._sub_width, self._sub_height),
114            background=False,
115        )
116        index = 0
117        mask_texture = ba.gettexture('characterIconMask')
118        for y in range(rows):
119            for x in range(columns):
120                pos = (
121                    x * (button_width + 2 * button_buffer_h) + button_buffer_h,
122                    self._sub_height
123                    - (y + 1) * (button_height + 2 * button_buffer_v)
124                    + 12,
125                )
126                btn = ba.buttonwidget(
127                    parent=self._subcontainer,
128                    button_type='square',
129                    size=(button_width, button_height),
130                    autoselect=True,
131                    texture=self._icon_textures[index],
132                    tint_texture=self._icon_tint_textures[index],
133                    mask_texture=mask_texture,
134                    label='',
135                    color=(1, 1, 1),
136                    tint_color=tint_color,
137                    tint2_color=tint2_color,
138                    on_activate_call=ba.Call(
139                        self._select_character, self._spazzes[index]
140                    ),
141                    position=pos,
142                )
143                ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
144                if self._spazzes[index] == selected_character:
145                    ba.containerwidget(
146                        edit=self._subcontainer,
147                        selected_child=btn,
148                        visible_child=btn,
149                    )
150                name = ba.Lstr(
151                    translate=('characterNames', self._spazzes[index])
152                )
153                ba.textwidget(
154                    parent=self._subcontainer,
155                    text=name,
156                    position=(pos[0] + button_width * 0.5, pos[1] - 12),
157                    size=(0, 0),
158                    scale=0.5,
159                    maxwidth=button_width,
160                    draw_controller=btn,
161                    h_align='center',
162                    v_align='center',
163                    color=(0.8, 0.8, 0.8, 0.8),
164                )
165                index += 1
166
167                if index >= count:
168                    break
169            if index >= count:
170                break
171        self._get_more_characters_button = btn = ba.buttonwidget(
172            parent=self._subcontainer,
173            size=(self._sub_width * 0.8, 60),
174            position=(self._sub_width * 0.1, 30),
175            label=ba.Lstr(resource='editProfileWindow.getMoreCharactersText'),
176            on_activate_call=self._on_store_press,
177            color=(0.6, 0.6, 0.6),
178            textcolor=(0.8, 0.8, 0.8),
179            autoselect=True,
180        )
181        ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30)
def on_popup_cancel(self) -> None:
207    def on_popup_cancel(self) -> None:
208        ba.playsound(ba.getsound('swish'))
209        self._transition_out()

Called when the popup is canceled.

Cancels can occur due to clicking outside the window, hitting escape, etc.