bastd.ui.colorpicker

Provides popup windows for choosing colors.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides popup windows for choosing colors."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING
  8
  9import ba
 10from bastd.ui.popup import PopupWindow
 11
 12if TYPE_CHECKING:
 13    from typing import Any, Sequence
 14
 15
 16class ColorPicker(PopupWindow):
 17    """A popup UI to select from a set of colors.
 18
 19    Passes the color to the delegate's color_picker_selected_color() method.
 20    """
 21
 22    def __init__(self,
 23                 parent: ba.Widget,
 24                 position: tuple[float, float],
 25                 initial_color: Sequence[float] = (1.0, 1.0, 1.0),
 26                 delegate: Any = None,
 27                 scale: float | None = None,
 28                 offset: tuple[float, float] = (0.0, 0.0),
 29                 tag: Any = ''):
 30        # pylint: disable=too-many-locals
 31        from ba.internal import get_player_colors
 32
 33        c_raw = get_player_colors()
 34        assert len(c_raw) == 16
 35        self.colors = [c_raw[0:4], c_raw[4:8], c_raw[8:12], c_raw[12:16]]
 36
 37        uiscale = ba.app.ui.uiscale
 38        if scale is None:
 39            scale = (2.3 if uiscale is ba.UIScale.SMALL else
 40                     1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
 41        self._parent = parent
 42        self._position = position
 43        self._scale = scale
 44        self._offset = offset
 45        self._delegate = delegate
 46        self._transitioning_out = False
 47        self._tag = tag
 48        self._initial_color = initial_color
 49
 50        # Create our _root_widget.
 51        PopupWindow.__init__(self,
 52                             position=position,
 53                             size=(210, 240),
 54                             scale=scale,
 55                             focus_position=(10, 10),
 56                             focus_size=(190, 220),
 57                             bg_color=(0.5, 0.5, 0.5),
 58                             offset=offset)
 59        rows: list[list[ba.Widget]] = []
 60        closest_dist = 9999.0
 61        closest = (0, 0)
 62        for y in range(4):
 63            row: list[ba.Widget] = []
 64            rows.append(row)
 65            for x in range(4):
 66                color = self.colors[y][x]
 67                dist = (abs(color[0] - initial_color[0]) +
 68                        abs(color[1] - initial_color[1]) +
 69                        abs(color[2] - initial_color[2]))
 70                if dist < closest_dist:
 71                    closest = (x, y)
 72                    closest_dist = dist
 73                btn = ba.buttonwidget(parent=self.root_widget,
 74                                      position=(22 + 45 * x, 185 - 45 * y),
 75                                      size=(35, 40),
 76                                      label='',
 77                                      button_type='square',
 78                                      on_activate_call=ba.WeakCall(
 79                                          self._select, x, y),
 80                                      autoselect=True,
 81                                      color=color,
 82                                      extra_touch_border_scale=0.0)
 83                row.append(btn)
 84        other_button = ba.buttonwidget(
 85            parent=self.root_widget,
 86            position=(105 - 60, 13),
 87            color=(0.7, 0.7, 0.7),
 88            text_scale=0.5,
 89            textcolor=(0.8, 0.8, 0.8),
 90            size=(120, 30),
 91            label=ba.Lstr(resource='otherText',
 92                          fallback_resource='coopSelectWindow.customText'),
 93            autoselect=True,
 94            on_activate_call=ba.WeakCall(self._select_other))
 95
 96        # Custom colors are limited to pro currently.
 97        if not ba.app.accounts_v1.have_pro():
 98            ba.imagewidget(parent=self.root_widget,
 99                           position=(50, 12),
100                           size=(30, 30),
101                           texture=ba.gettexture('lock'),
102                           draw_controller=other_button)
103
104        # If their color is close to one of our swatches, select it.
105        # Otherwise select 'other'.
106        if closest_dist < 0.03:
107            ba.containerwidget(edit=self.root_widget,
108                               selected_child=rows[closest[1]][closest[0]])
109        else:
110            ba.containerwidget(edit=self.root_widget,
111                               selected_child=other_button)
112
113    def get_tag(self) -> Any:
114        """Return this popup's tag."""
115        return self._tag
116
117    def _select_other(self) -> None:
118        from bastd.ui import purchase
119
120        # Requires pro.
121        if not ba.app.accounts_v1.have_pro():
122            purchase.PurchaseWindow(items=['pro'])
123            self._transition_out()
124            return
125        ColorPickerExact(parent=self._parent,
126                         position=self._position,
127                         initial_color=self._initial_color,
128                         delegate=self._delegate,
129                         scale=self._scale,
130                         offset=self._offset,
131                         tag=self._tag)
132
133        # New picker now 'owns' the delegate; we shouldn't send it any
134        # more messages.
135        self._delegate = None
136        self._transition_out()
137
138    def _select(self, x: int, y: int) -> None:
139        if self._delegate:
140            self._delegate.color_picker_selected_color(self, self.colors[y][x])
141        ba.timer(0.05, self._transition_out, timetype=ba.TimeType.REAL)
142
143    def _transition_out(self) -> None:
144        if not self._transitioning_out:
145            self._transitioning_out = True
146            if self._delegate is not None:
147                self._delegate.color_picker_closing(self)
148            ba.containerwidget(edit=self.root_widget, transition='out_scale')
149
150    def on_popup_cancel(self) -> None:
151        if not self._transitioning_out:
152            ba.playsound(ba.getsound('swish'))
153        self._transition_out()
154
155
156class ColorPickerExact(PopupWindow):
157    """ pops up a ui to select from a set of colors.
158    passes the color to the delegate's color_picker_selected_color() method """
159
160    def __init__(self,
161                 parent: ba.Widget,
162                 position: tuple[float, float],
163                 initial_color: Sequence[float] = (1.0, 1.0, 1.0),
164                 delegate: Any = None,
165                 scale: float | None = None,
166                 offset: tuple[float, float] = (0.0, 0.0),
167                 tag: Any = ''):
168        # pylint: disable=too-many-locals
169        del parent  # Unused var.
170        from ba.internal import get_player_colors
171        c_raw = get_player_colors()
172        assert len(c_raw) == 16
173        self.colors = [c_raw[0:4], c_raw[4:8], c_raw[8:12], c_raw[12:16]]
174
175        uiscale = ba.app.ui.uiscale
176        if scale is None:
177            scale = (2.3 if uiscale is ba.UIScale.SMALL else
178                     1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
179        self._delegate = delegate
180        self._transitioning_out = False
181        self._tag = tag
182        self._color = list(initial_color)
183        self._last_press_time = ba.time(ba.TimeType.REAL,
184                                        ba.TimeFormat.MILLISECONDS)
185        self._last_press_color_name: str | None = None
186        self._last_press_increasing: bool | None = None
187        self._change_speed = 1.0
188        width = 180.0
189        height = 240.0
190
191        # Creates our _root_widget.
192        PopupWindow.__init__(self,
193                             position=position,
194                             size=(width, height),
195                             scale=scale,
196                             focus_position=(10, 10),
197                             focus_size=(width - 20, height - 20),
198                             bg_color=(0.5, 0.5, 0.5),
199                             offset=offset)
200        self._swatch = ba.imagewidget(parent=self.root_widget,
201                                      position=(width * 0.5 - 50, height - 70),
202                                      size=(100, 70),
203                                      texture=ba.gettexture('buttonSquare'),
204                                      color=(1, 0, 0))
205        x = 50
206        y = height - 90
207        self._label_r: ba.Widget
208        self._label_g: ba.Widget
209        self._label_b: ba.Widget
210        for color_name, color_val in [('r', (1, 0.15, 0.15)),
211                                      ('g', (0.15, 1, 0.15)),
212                                      ('b', (0.15, 0.15, 1))]:
213            txt = ba.textwidget(parent=self.root_widget,
214                                position=(x - 10, y),
215                                size=(0, 0),
216                                h_align='center',
217                                color=color_val,
218                                v_align='center',
219                                text='0.12')
220            setattr(self, '_label_' + color_name, txt)
221            for b_label, bhval, binc in [('-', 30, False), ('+', 75, True)]:
222                ba.buttonwidget(parent=self.root_widget,
223                                position=(x + bhval, y - 15),
224                                scale=0.8,
225                                repeat=True,
226                                text_scale=1.3,
227                                size=(40, 40),
228                                label=b_label,
229                                autoselect=True,
230                                enable_sound=False,
231                                on_activate_call=ba.WeakCall(
232                                    self._color_change_press, color_name,
233                                    binc))
234            y -= 42
235
236        btn = ba.buttonwidget(parent=self.root_widget,
237                              position=(width * 0.5 - 40, 10),
238                              size=(80, 30),
239                              text_scale=0.6,
240                              color=(0.6, 0.6, 0.6),
241                              textcolor=(0.7, 0.7, 0.7),
242                              label=ba.Lstr(resource='doneText'),
243                              on_activate_call=ba.WeakCall(
244                                  self._transition_out),
245                              autoselect=True)
246        ba.containerwidget(edit=self.root_widget, start_button=btn)
247
248        # Unlike the swatch picker, we stay open and constantly push our
249        # color to the delegate, so start doing that.
250        self._update_for_color()
251
252    # noinspection PyUnresolvedReferences
253    def _update_for_color(self) -> None:
254        if not self.root_widget:
255            return
256        ba.imagewidget(edit=self._swatch, color=self._color)
257
258        # We generate these procedurally, so pylint misses them.
259        # FIXME: create static attrs instead.
260        # pylint: disable=consider-using-f-string
261        ba.textwidget(edit=self._label_r, text='%.2f' % self._color[0])
262        ba.textwidget(edit=self._label_g, text='%.2f' % self._color[1])
263        ba.textwidget(edit=self._label_b, text='%.2f' % self._color[2])
264        if self._delegate is not None:
265            self._delegate.color_picker_selected_color(self, self._color)
266
267    def _color_change_press(self, color_name: str, increasing: bool) -> None:
268        # If we get rapid-fire presses, eventually start moving faster.
269        current_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
270        since_last = current_time - self._last_press_time
271        if (since_last < 200 and self._last_press_color_name == color_name
272                and self._last_press_increasing == increasing):
273            self._change_speed += 0.25
274        else:
275            self._change_speed = 1.0
276        self._last_press_time = current_time
277        self._last_press_color_name = color_name
278        self._last_press_increasing = increasing
279
280        color_index = ('r', 'g', 'b').index(color_name)
281        offs = int(self._change_speed) * (0.01 if increasing else -0.01)
282        self._color[color_index] = max(
283            0.0, min(1.0, self._color[color_index] + offs))
284        self._update_for_color()
285
286    def get_tag(self) -> Any:
287        """Return this popup's tag value."""
288        return self._tag
289
290    def _transition_out(self) -> None:
291        if not self._transitioning_out:
292            self._transitioning_out = True
293            if self._delegate is not None:
294                self._delegate.color_picker_closing(self)
295            ba.containerwidget(edit=self.root_widget, transition='out_scale')
296
297    def on_popup_cancel(self) -> None:
298        if not self._transitioning_out:
299            ba.playsound(ba.getsound('swish'))
300        self._transition_out()
class ColorPicker(bastd.ui.popup.PopupWindow):
 17class ColorPicker(PopupWindow):
 18    """A popup UI to select from a set of colors.
 19
 20    Passes the color to the delegate's color_picker_selected_color() method.
 21    """
 22
 23    def __init__(self,
 24                 parent: ba.Widget,
 25                 position: tuple[float, float],
 26                 initial_color: Sequence[float] = (1.0, 1.0, 1.0),
 27                 delegate: Any = None,
 28                 scale: float | None = None,
 29                 offset: tuple[float, float] = (0.0, 0.0),
 30                 tag: Any = ''):
 31        # pylint: disable=too-many-locals
 32        from ba.internal import get_player_colors
 33
 34        c_raw = get_player_colors()
 35        assert len(c_raw) == 16
 36        self.colors = [c_raw[0:4], c_raw[4:8], c_raw[8:12], c_raw[12:16]]
 37
 38        uiscale = ba.app.ui.uiscale
 39        if scale is None:
 40            scale = (2.3 if uiscale is ba.UIScale.SMALL else
 41                     1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
 42        self._parent = parent
 43        self._position = position
 44        self._scale = scale
 45        self._offset = offset
 46        self._delegate = delegate
 47        self._transitioning_out = False
 48        self._tag = tag
 49        self._initial_color = initial_color
 50
 51        # Create our _root_widget.
 52        PopupWindow.__init__(self,
 53                             position=position,
 54                             size=(210, 240),
 55                             scale=scale,
 56                             focus_position=(10, 10),
 57                             focus_size=(190, 220),
 58                             bg_color=(0.5, 0.5, 0.5),
 59                             offset=offset)
 60        rows: list[list[ba.Widget]] = []
 61        closest_dist = 9999.0
 62        closest = (0, 0)
 63        for y in range(4):
 64            row: list[ba.Widget] = []
 65            rows.append(row)
 66            for x in range(4):
 67                color = self.colors[y][x]
 68                dist = (abs(color[0] - initial_color[0]) +
 69                        abs(color[1] - initial_color[1]) +
 70                        abs(color[2] - initial_color[2]))
 71                if dist < closest_dist:
 72                    closest = (x, y)
 73                    closest_dist = dist
 74                btn = ba.buttonwidget(parent=self.root_widget,
 75                                      position=(22 + 45 * x, 185 - 45 * y),
 76                                      size=(35, 40),
 77                                      label='',
 78                                      button_type='square',
 79                                      on_activate_call=ba.WeakCall(
 80                                          self._select, x, y),
 81                                      autoselect=True,
 82                                      color=color,
 83                                      extra_touch_border_scale=0.0)
 84                row.append(btn)
 85        other_button = ba.buttonwidget(
 86            parent=self.root_widget,
 87            position=(105 - 60, 13),
 88            color=(0.7, 0.7, 0.7),
 89            text_scale=0.5,
 90            textcolor=(0.8, 0.8, 0.8),
 91            size=(120, 30),
 92            label=ba.Lstr(resource='otherText',
 93                          fallback_resource='coopSelectWindow.customText'),
 94            autoselect=True,
 95            on_activate_call=ba.WeakCall(self._select_other))
 96
 97        # Custom colors are limited to pro currently.
 98        if not ba.app.accounts_v1.have_pro():
 99            ba.imagewidget(parent=self.root_widget,
100                           position=(50, 12),
101                           size=(30, 30),
102                           texture=ba.gettexture('lock'),
103                           draw_controller=other_button)
104
105        # If their color is close to one of our swatches, select it.
106        # Otherwise select 'other'.
107        if closest_dist < 0.03:
108            ba.containerwidget(edit=self.root_widget,
109                               selected_child=rows[closest[1]][closest[0]])
110        else:
111            ba.containerwidget(edit=self.root_widget,
112                               selected_child=other_button)
113
114    def get_tag(self) -> Any:
115        """Return this popup's tag."""
116        return self._tag
117
118    def _select_other(self) -> None:
119        from bastd.ui import purchase
120
121        # Requires pro.
122        if not ba.app.accounts_v1.have_pro():
123            purchase.PurchaseWindow(items=['pro'])
124            self._transition_out()
125            return
126        ColorPickerExact(parent=self._parent,
127                         position=self._position,
128                         initial_color=self._initial_color,
129                         delegate=self._delegate,
130                         scale=self._scale,
131                         offset=self._offset,
132                         tag=self._tag)
133
134        # New picker now 'owns' the delegate; we shouldn't send it any
135        # more messages.
136        self._delegate = None
137        self._transition_out()
138
139    def _select(self, x: int, y: int) -> None:
140        if self._delegate:
141            self._delegate.color_picker_selected_color(self, self.colors[y][x])
142        ba.timer(0.05, self._transition_out, timetype=ba.TimeType.REAL)
143
144    def _transition_out(self) -> None:
145        if not self._transitioning_out:
146            self._transitioning_out = True
147            if self._delegate is not None:
148                self._delegate.color_picker_closing(self)
149            ba.containerwidget(edit=self.root_widget, transition='out_scale')
150
151    def on_popup_cancel(self) -> None:
152        if not self._transitioning_out:
153            ba.playsound(ba.getsound('swish'))
154        self._transition_out()

A popup UI to select from a set of colors.

Passes the color to the delegate's color_picker_selected_color() method.

ColorPicker( parent: _ba.Widget, position: tuple[float, float], initial_color: Sequence[float] = (1.0, 1.0, 1.0), delegate: Any = None, scale: float | None = None, offset: tuple[float, float] = (0.0, 0.0), tag: Any = '')
 23    def __init__(self,
 24                 parent: ba.Widget,
 25                 position: tuple[float, float],
 26                 initial_color: Sequence[float] = (1.0, 1.0, 1.0),
 27                 delegate: Any = None,
 28                 scale: float | None = None,
 29                 offset: tuple[float, float] = (0.0, 0.0),
 30                 tag: Any = ''):
 31        # pylint: disable=too-many-locals
 32        from ba.internal import get_player_colors
 33
 34        c_raw = get_player_colors()
 35        assert len(c_raw) == 16
 36        self.colors = [c_raw[0:4], c_raw[4:8], c_raw[8:12], c_raw[12:16]]
 37
 38        uiscale = ba.app.ui.uiscale
 39        if scale is None:
 40            scale = (2.3 if uiscale is ba.UIScale.SMALL else
 41                     1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
 42        self._parent = parent
 43        self._position = position
 44        self._scale = scale
 45        self._offset = offset
 46        self._delegate = delegate
 47        self._transitioning_out = False
 48        self._tag = tag
 49        self._initial_color = initial_color
 50
 51        # Create our _root_widget.
 52        PopupWindow.__init__(self,
 53                             position=position,
 54                             size=(210, 240),
 55                             scale=scale,
 56                             focus_position=(10, 10),
 57                             focus_size=(190, 220),
 58                             bg_color=(0.5, 0.5, 0.5),
 59                             offset=offset)
 60        rows: list[list[ba.Widget]] = []
 61        closest_dist = 9999.0
 62        closest = (0, 0)
 63        for y in range(4):
 64            row: list[ba.Widget] = []
 65            rows.append(row)
 66            for x in range(4):
 67                color = self.colors[y][x]
 68                dist = (abs(color[0] - initial_color[0]) +
 69                        abs(color[1] - initial_color[1]) +
 70                        abs(color[2] - initial_color[2]))
 71                if dist < closest_dist:
 72                    closest = (x, y)
 73                    closest_dist = dist
 74                btn = ba.buttonwidget(parent=self.root_widget,
 75                                      position=(22 + 45 * x, 185 - 45 * y),
 76                                      size=(35, 40),
 77                                      label='',
 78                                      button_type='square',
 79                                      on_activate_call=ba.WeakCall(
 80                                          self._select, x, y),
 81                                      autoselect=True,
 82                                      color=color,
 83                                      extra_touch_border_scale=0.0)
 84                row.append(btn)
 85        other_button = ba.buttonwidget(
 86            parent=self.root_widget,
 87            position=(105 - 60, 13),
 88            color=(0.7, 0.7, 0.7),
 89            text_scale=0.5,
 90            textcolor=(0.8, 0.8, 0.8),
 91            size=(120, 30),
 92            label=ba.Lstr(resource='otherText',
 93                          fallback_resource='coopSelectWindow.customText'),
 94            autoselect=True,
 95            on_activate_call=ba.WeakCall(self._select_other))
 96
 97        # Custom colors are limited to pro currently.
 98        if not ba.app.accounts_v1.have_pro():
 99            ba.imagewidget(parent=self.root_widget,
100                           position=(50, 12),
101                           size=(30, 30),
102                           texture=ba.gettexture('lock'),
103                           draw_controller=other_button)
104
105        # If their color is close to one of our swatches, select it.
106        # Otherwise select 'other'.
107        if closest_dist < 0.03:
108            ba.containerwidget(edit=self.root_widget,
109                               selected_child=rows[closest[1]][closest[0]])
110        else:
111            ba.containerwidget(edit=self.root_widget,
112                               selected_child=other_button)
def get_tag(self) -> Any:
114    def get_tag(self) -> Any:
115        """Return this popup's tag."""
116        return self._tag

Return this popup's tag.

def on_popup_cancel(self) -> None:
151    def on_popup_cancel(self) -> None:
152        if not self._transitioning_out:
153            ba.playsound(ba.getsound('swish'))
154        self._transition_out()

Called when the popup is canceled.

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

class ColorPickerExact(bastd.ui.popup.PopupWindow):
157class ColorPickerExact(PopupWindow):
158    """ pops up a ui to select from a set of colors.
159    passes the color to the delegate's color_picker_selected_color() method """
160
161    def __init__(self,
162                 parent: ba.Widget,
163                 position: tuple[float, float],
164                 initial_color: Sequence[float] = (1.0, 1.0, 1.0),
165                 delegate: Any = None,
166                 scale: float | None = None,
167                 offset: tuple[float, float] = (0.0, 0.0),
168                 tag: Any = ''):
169        # pylint: disable=too-many-locals
170        del parent  # Unused var.
171        from ba.internal import get_player_colors
172        c_raw = get_player_colors()
173        assert len(c_raw) == 16
174        self.colors = [c_raw[0:4], c_raw[4:8], c_raw[8:12], c_raw[12:16]]
175
176        uiscale = ba.app.ui.uiscale
177        if scale is None:
178            scale = (2.3 if uiscale is ba.UIScale.SMALL else
179                     1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
180        self._delegate = delegate
181        self._transitioning_out = False
182        self._tag = tag
183        self._color = list(initial_color)
184        self._last_press_time = ba.time(ba.TimeType.REAL,
185                                        ba.TimeFormat.MILLISECONDS)
186        self._last_press_color_name: str | None = None
187        self._last_press_increasing: bool | None = None
188        self._change_speed = 1.0
189        width = 180.0
190        height = 240.0
191
192        # Creates our _root_widget.
193        PopupWindow.__init__(self,
194                             position=position,
195                             size=(width, height),
196                             scale=scale,
197                             focus_position=(10, 10),
198                             focus_size=(width - 20, height - 20),
199                             bg_color=(0.5, 0.5, 0.5),
200                             offset=offset)
201        self._swatch = ba.imagewidget(parent=self.root_widget,
202                                      position=(width * 0.5 - 50, height - 70),
203                                      size=(100, 70),
204                                      texture=ba.gettexture('buttonSquare'),
205                                      color=(1, 0, 0))
206        x = 50
207        y = height - 90
208        self._label_r: ba.Widget
209        self._label_g: ba.Widget
210        self._label_b: ba.Widget
211        for color_name, color_val in [('r', (1, 0.15, 0.15)),
212                                      ('g', (0.15, 1, 0.15)),
213                                      ('b', (0.15, 0.15, 1))]:
214            txt = ba.textwidget(parent=self.root_widget,
215                                position=(x - 10, y),
216                                size=(0, 0),
217                                h_align='center',
218                                color=color_val,
219                                v_align='center',
220                                text='0.12')
221            setattr(self, '_label_' + color_name, txt)
222            for b_label, bhval, binc in [('-', 30, False), ('+', 75, True)]:
223                ba.buttonwidget(parent=self.root_widget,
224                                position=(x + bhval, y - 15),
225                                scale=0.8,
226                                repeat=True,
227                                text_scale=1.3,
228                                size=(40, 40),
229                                label=b_label,
230                                autoselect=True,
231                                enable_sound=False,
232                                on_activate_call=ba.WeakCall(
233                                    self._color_change_press, color_name,
234                                    binc))
235            y -= 42
236
237        btn = ba.buttonwidget(parent=self.root_widget,
238                              position=(width * 0.5 - 40, 10),
239                              size=(80, 30),
240                              text_scale=0.6,
241                              color=(0.6, 0.6, 0.6),
242                              textcolor=(0.7, 0.7, 0.7),
243                              label=ba.Lstr(resource='doneText'),
244                              on_activate_call=ba.WeakCall(
245                                  self._transition_out),
246                              autoselect=True)
247        ba.containerwidget(edit=self.root_widget, start_button=btn)
248
249        # Unlike the swatch picker, we stay open and constantly push our
250        # color to the delegate, so start doing that.
251        self._update_for_color()
252
253    # noinspection PyUnresolvedReferences
254    def _update_for_color(self) -> None:
255        if not self.root_widget:
256            return
257        ba.imagewidget(edit=self._swatch, color=self._color)
258
259        # We generate these procedurally, so pylint misses them.
260        # FIXME: create static attrs instead.
261        # pylint: disable=consider-using-f-string
262        ba.textwidget(edit=self._label_r, text='%.2f' % self._color[0])
263        ba.textwidget(edit=self._label_g, text='%.2f' % self._color[1])
264        ba.textwidget(edit=self._label_b, text='%.2f' % self._color[2])
265        if self._delegate is not None:
266            self._delegate.color_picker_selected_color(self, self._color)
267
268    def _color_change_press(self, color_name: str, increasing: bool) -> None:
269        # If we get rapid-fire presses, eventually start moving faster.
270        current_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
271        since_last = current_time - self._last_press_time
272        if (since_last < 200 and self._last_press_color_name == color_name
273                and self._last_press_increasing == increasing):
274            self._change_speed += 0.25
275        else:
276            self._change_speed = 1.0
277        self._last_press_time = current_time
278        self._last_press_color_name = color_name
279        self._last_press_increasing = increasing
280
281        color_index = ('r', 'g', 'b').index(color_name)
282        offs = int(self._change_speed) * (0.01 if increasing else -0.01)
283        self._color[color_index] = max(
284            0.0, min(1.0, self._color[color_index] + offs))
285        self._update_for_color()
286
287    def get_tag(self) -> Any:
288        """Return this popup's tag value."""
289        return self._tag
290
291    def _transition_out(self) -> None:
292        if not self._transitioning_out:
293            self._transitioning_out = True
294            if self._delegate is not None:
295                self._delegate.color_picker_closing(self)
296            ba.containerwidget(edit=self.root_widget, transition='out_scale')
297
298    def on_popup_cancel(self) -> None:
299        if not self._transitioning_out:
300            ba.playsound(ba.getsound('swish'))
301        self._transition_out()

pops up a ui to select from a set of colors. passes the color to the delegate's color_picker_selected_color() method

ColorPickerExact( parent: _ba.Widget, position: tuple[float, float], initial_color: Sequence[float] = (1.0, 1.0, 1.0), delegate: Any = None, scale: float | None = None, offset: tuple[float, float] = (0.0, 0.0), tag: Any = '')
161    def __init__(self,
162                 parent: ba.Widget,
163                 position: tuple[float, float],
164                 initial_color: Sequence[float] = (1.0, 1.0, 1.0),
165                 delegate: Any = None,
166                 scale: float | None = None,
167                 offset: tuple[float, float] = (0.0, 0.0),
168                 tag: Any = ''):
169        # pylint: disable=too-many-locals
170        del parent  # Unused var.
171        from ba.internal import get_player_colors
172        c_raw = get_player_colors()
173        assert len(c_raw) == 16
174        self.colors = [c_raw[0:4], c_raw[4:8], c_raw[8:12], c_raw[12:16]]
175
176        uiscale = ba.app.ui.uiscale
177        if scale is None:
178            scale = (2.3 if uiscale is ba.UIScale.SMALL else
179                     1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
180        self._delegate = delegate
181        self._transitioning_out = False
182        self._tag = tag
183        self._color = list(initial_color)
184        self._last_press_time = ba.time(ba.TimeType.REAL,
185                                        ba.TimeFormat.MILLISECONDS)
186        self._last_press_color_name: str | None = None
187        self._last_press_increasing: bool | None = None
188        self._change_speed = 1.0
189        width = 180.0
190        height = 240.0
191
192        # Creates our _root_widget.
193        PopupWindow.__init__(self,
194                             position=position,
195                             size=(width, height),
196                             scale=scale,
197                             focus_position=(10, 10),
198                             focus_size=(width - 20, height - 20),
199                             bg_color=(0.5, 0.5, 0.5),
200                             offset=offset)
201        self._swatch = ba.imagewidget(parent=self.root_widget,
202                                      position=(width * 0.5 - 50, height - 70),
203                                      size=(100, 70),
204                                      texture=ba.gettexture('buttonSquare'),
205                                      color=(1, 0, 0))
206        x = 50
207        y = height - 90
208        self._label_r: ba.Widget
209        self._label_g: ba.Widget
210        self._label_b: ba.Widget
211        for color_name, color_val in [('r', (1, 0.15, 0.15)),
212                                      ('g', (0.15, 1, 0.15)),
213                                      ('b', (0.15, 0.15, 1))]:
214            txt = ba.textwidget(parent=self.root_widget,
215                                position=(x - 10, y),
216                                size=(0, 0),
217                                h_align='center',
218                                color=color_val,
219                                v_align='center',
220                                text='0.12')
221            setattr(self, '_label_' + color_name, txt)
222            for b_label, bhval, binc in [('-', 30, False), ('+', 75, True)]:
223                ba.buttonwidget(parent=self.root_widget,
224                                position=(x + bhval, y - 15),
225                                scale=0.8,
226                                repeat=True,
227                                text_scale=1.3,
228                                size=(40, 40),
229                                label=b_label,
230                                autoselect=True,
231                                enable_sound=False,
232                                on_activate_call=ba.WeakCall(
233                                    self._color_change_press, color_name,
234                                    binc))
235            y -= 42
236
237        btn = ba.buttonwidget(parent=self.root_widget,
238                              position=(width * 0.5 - 40, 10),
239                              size=(80, 30),
240                              text_scale=0.6,
241                              color=(0.6, 0.6, 0.6),
242                              textcolor=(0.7, 0.7, 0.7),
243                              label=ba.Lstr(resource='doneText'),
244                              on_activate_call=ba.WeakCall(
245                                  self._transition_out),
246                              autoselect=True)
247        ba.containerwidget(edit=self.root_widget, start_button=btn)
248
249        # Unlike the swatch picker, we stay open and constantly push our
250        # color to the delegate, so start doing that.
251        self._update_for_color()
def get_tag(self) -> Any:
287    def get_tag(self) -> Any:
288        """Return this popup's tag value."""
289        return self._tag

Return this popup's tag value.

def on_popup_cancel(self) -> None:
298    def on_popup_cancel(self) -> None:
299        if not self._transitioning_out:
300            ba.playsound(ba.getsound('swish'))
301        self._transition_out()

Called when the popup is canceled.

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