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

Delegate for character-picker.

def on_character_picker_pick(self, character: str) -> None:
21    def on_character_picker_pick(self, character: str) -> None:
22        """Called when a character is selected."""
23        raise NotImplementedError()

Called when a character is selected.

def on_character_picker_get_more_press(self) -> None:
25    def on_character_picker_get_more_press(self) -> None:
26        """Called when the 'get more characters' button is pressed."""
27        raise NotImplementedError()

Called when the 'get more characters' button is pressed.

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

Popup window for selecting characters.

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

Called when the popup is canceled.

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