mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-11-07 17:36:08 +00:00
Added new files
This commit is contained in:
parent
5e004af549
commit
77ccb73089
1783 changed files with 566966 additions and 0 deletions
1
dist/ba_data/python/bastd/ui/soundtrack/__init__.py
vendored
Normal file
1
dist/ba_data/python/bastd/ui/soundtrack/__init__.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
570
dist/ba_data/python/bastd/ui/soundtrack/browser.py
vendored
Normal file
570
dist/ba_data/python/bastd/ui/soundtrack/browser.py
vendored
Normal file
|
|
@ -0,0 +1,570 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides UI for browsing soundtracks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SoundtrackBrowserWindow(ba.Window):
|
||||
"""Window for browsing soundtracks."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: ba.Widget | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
# If they provided an origin-widget, scale up from that.
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
|
||||
self._r = 'editSoundtrackWindow'
|
||||
uiscale = ba.app.ui.uiscale
|
||||
self._width = 800 if uiscale is ba.UIScale.SMALL else 600
|
||||
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
340
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 370
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 440
|
||||
)
|
||||
spacing = 40.0
|
||||
v = self._height - 40.0
|
||||
v -= spacing * 1.0
|
||||
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
transition=transition,
|
||||
toolbar_visibility='menu_minimal',
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
scale=(
|
||||
2.3
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.6
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, -18)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
|
||||
if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
|
||||
self._back_button = None
|
||||
else:
|
||||
self._back_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(45 + x_inset, self._height - 60),
|
||||
size=(120, 60),
|
||||
scale=0.8,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
autoselect=True,
|
||||
)
|
||||
ba.buttonwidget(
|
||||
edit=self._back_button,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 35),
|
||||
size=(0, 0),
|
||||
maxwidth=300,
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
h = 43 + x_inset
|
||||
v = self._height - 60
|
||||
b_color = (0.6, 0.53, 0.63)
|
||||
b_textcolor = (0.75, 0.7, 0.8)
|
||||
lock_tex = ba.gettexture('lock')
|
||||
self._lock_images: list[ba.Widget] = []
|
||||
|
||||
scl = (
|
||||
1.0
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.13
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.4
|
||||
)
|
||||
v -= 60.0 * scl
|
||||
self._new_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(h, v),
|
||||
size=(100, 55.0 * scl),
|
||||
on_activate_call=self._new_soundtrack,
|
||||
color=b_color,
|
||||
button_type='square',
|
||||
autoselect=True,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.7,
|
||||
label=ba.Lstr(resource=self._r + '.newText'),
|
||||
)
|
||||
self._lock_images.append(
|
||||
ba.imagewidget(
|
||||
parent=self._root_widget,
|
||||
size=(30, 30),
|
||||
draw_controller=btn,
|
||||
position=(h - 10, v + 55.0 * scl - 28),
|
||||
texture=lock_tex,
|
||||
)
|
||||
)
|
||||
|
||||
if self._back_button is None:
|
||||
ba.widget(
|
||||
edit=btn,
|
||||
left_widget=ba.internal.get_special_widget('back_button'),
|
||||
)
|
||||
v -= 60.0 * scl
|
||||
|
||||
self._edit_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(h, v),
|
||||
size=(100, 55.0 * scl),
|
||||
on_activate_call=self._edit_soundtrack,
|
||||
color=b_color,
|
||||
button_type='square',
|
||||
autoselect=True,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.7,
|
||||
label=ba.Lstr(resource=self._r + '.editText'),
|
||||
)
|
||||
self._lock_images.append(
|
||||
ba.imagewidget(
|
||||
parent=self._root_widget,
|
||||
size=(30, 30),
|
||||
draw_controller=btn,
|
||||
position=(h - 10, v + 55.0 * scl - 28),
|
||||
texture=lock_tex,
|
||||
)
|
||||
)
|
||||
if self._back_button is None:
|
||||
ba.widget(
|
||||
edit=btn,
|
||||
left_widget=ba.internal.get_special_widget('back_button'),
|
||||
)
|
||||
v -= 60.0 * scl
|
||||
|
||||
self._duplicate_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(h, v),
|
||||
size=(100, 55.0 * scl),
|
||||
on_activate_call=self._duplicate_soundtrack,
|
||||
button_type='square',
|
||||
autoselect=True,
|
||||
color=b_color,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.7,
|
||||
label=ba.Lstr(resource=self._r + '.duplicateText'),
|
||||
)
|
||||
self._lock_images.append(
|
||||
ba.imagewidget(
|
||||
parent=self._root_widget,
|
||||
size=(30, 30),
|
||||
draw_controller=btn,
|
||||
position=(h - 10, v + 55.0 * scl - 28),
|
||||
texture=lock_tex,
|
||||
)
|
||||
)
|
||||
if self._back_button is None:
|
||||
ba.widget(
|
||||
edit=btn,
|
||||
left_widget=ba.internal.get_special_widget('back_button'),
|
||||
)
|
||||
v -= 60.0 * scl
|
||||
|
||||
self._delete_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(h, v),
|
||||
size=(100, 55.0 * scl),
|
||||
on_activate_call=self._delete_soundtrack,
|
||||
color=b_color,
|
||||
button_type='square',
|
||||
autoselect=True,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.7,
|
||||
label=ba.Lstr(resource=self._r + '.deleteText'),
|
||||
)
|
||||
self._lock_images.append(
|
||||
ba.imagewidget(
|
||||
parent=self._root_widget,
|
||||
size=(30, 30),
|
||||
draw_controller=btn,
|
||||
position=(h - 10, v + 55.0 * scl - 28),
|
||||
texture=lock_tex,
|
||||
)
|
||||
)
|
||||
if self._back_button is None:
|
||||
ba.widget(
|
||||
edit=btn,
|
||||
left_widget=ba.internal.get_special_widget('back_button'),
|
||||
)
|
||||
|
||||
# Keep our lock images up to date/etc.
|
||||
self._update_timer = ba.Timer(
|
||||
1.0,
|
||||
ba.WeakCall(self._update),
|
||||
timetype=ba.TimeType.REAL,
|
||||
repeat=True,
|
||||
)
|
||||
self._update()
|
||||
|
||||
v = self._height - 65
|
||||
scroll_height = self._height - 105
|
||||
v -= scroll_height
|
||||
self._scrollwidget = scrollwidget = ba.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
position=(152 + x_inset, v),
|
||||
highlight=False,
|
||||
size=(self._width - (205 + 2 * x_inset), scroll_height),
|
||||
)
|
||||
ba.widget(
|
||||
edit=self._scrollwidget,
|
||||
left_widget=self._new_button,
|
||||
right_widget=ba.internal.get_special_widget('party_button')
|
||||
if ba.app.ui.use_toolbars
|
||||
else self._scrollwidget,
|
||||
)
|
||||
self._col = ba.columnwidget(parent=scrollwidget, border=2, margin=0)
|
||||
|
||||
self._soundtracks: dict[str, Any] | None = None
|
||||
self._selected_soundtrack: str | None = None
|
||||
self._selected_soundtrack_index: int | None = None
|
||||
self._soundtrack_widgets: list[ba.Widget] = []
|
||||
self._allow_changing_soundtracks = False
|
||||
self._refresh()
|
||||
if self._back_button is not None:
|
||||
ba.buttonwidget(edit=self._back_button, on_activate_call=self._back)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._back_button
|
||||
)
|
||||
else:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self._back
|
||||
)
|
||||
|
||||
def _update(self) -> None:
|
||||
have = ba.app.accounts_v1.have_pro_options()
|
||||
for lock in self._lock_images:
|
||||
ba.imagewidget(edit=lock, opacity=0.0 if have else 1.0)
|
||||
|
||||
def _do_delete_soundtrack(self) -> None:
|
||||
cfg = ba.app.config
|
||||
soundtracks = cfg.setdefault('Soundtracks', {})
|
||||
if self._selected_soundtrack in soundtracks:
|
||||
del soundtracks[self._selected_soundtrack]
|
||||
cfg.commit()
|
||||
ba.playsound(ba.getsound('shieldDown'))
|
||||
assert self._selected_soundtrack_index is not None
|
||||
assert self._soundtracks is not None
|
||||
if self._selected_soundtrack_index >= len(self._soundtracks):
|
||||
self._selected_soundtrack_index = len(self._soundtracks)
|
||||
self._refresh()
|
||||
|
||||
def _delete_soundtrack(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
from bastd.ui.confirm import ConfirmWindow
|
||||
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
if self._selected_soundtrack is None:
|
||||
return
|
||||
if self._selected_soundtrack == '__default__':
|
||||
ba.playsound(ba.getsound('error'))
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource=self._r + '.cantDeleteDefaultText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
else:
|
||||
ConfirmWindow(
|
||||
ba.Lstr(
|
||||
resource=self._r + '.deleteConfirmText',
|
||||
subs=[('${NAME}', self._selected_soundtrack)],
|
||||
),
|
||||
self._do_delete_soundtrack,
|
||||
450,
|
||||
150,
|
||||
)
|
||||
|
||||
def _duplicate_soundtrack(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
cfg = ba.app.config
|
||||
cfg.setdefault('Soundtracks', {})
|
||||
|
||||
if self._selected_soundtrack is None:
|
||||
return
|
||||
sdtk: dict[str, Any]
|
||||
if self._selected_soundtrack == '__default__':
|
||||
sdtk = {}
|
||||
else:
|
||||
sdtk = cfg['Soundtracks'][self._selected_soundtrack]
|
||||
|
||||
# Find a valid dup name that doesn't exist.
|
||||
test_index = 1
|
||||
copy_text = ba.Lstr(resource='copyOfText').evaluate()
|
||||
# Get just 'Copy' or whatnot.
|
||||
copy_word = copy_text.replace('${NAME}', '').strip()
|
||||
base_name = self._get_soundtrack_display_name(
|
||||
self._selected_soundtrack
|
||||
).evaluate()
|
||||
assert isinstance(base_name, str)
|
||||
|
||||
# If it looks like a copy, strip digits and spaces off the end.
|
||||
if copy_word in base_name:
|
||||
while base_name[-1].isdigit() or base_name[-1] == ' ':
|
||||
base_name = base_name[:-1]
|
||||
while True:
|
||||
if copy_word in base_name:
|
||||
test_name = base_name
|
||||
else:
|
||||
test_name = copy_text.replace('${NAME}', base_name)
|
||||
if test_index > 1:
|
||||
test_name += ' ' + str(test_index)
|
||||
if test_name not in cfg['Soundtracks']:
|
||||
break
|
||||
test_index += 1
|
||||
|
||||
cfg['Soundtracks'][test_name] = copy.deepcopy(sdtk)
|
||||
cfg.commit()
|
||||
self._refresh(select_soundtrack=test_name)
|
||||
|
||||
def _select(self, name: str, index: int) -> None:
|
||||
music = ba.app.music
|
||||
self._selected_soundtrack_index = index
|
||||
self._selected_soundtrack = name
|
||||
cfg = ba.app.config
|
||||
current_soundtrack = cfg.setdefault('Soundtrack', '__default__')
|
||||
|
||||
# If it varies from current, commit and play.
|
||||
if current_soundtrack != name and self._allow_changing_soundtracks:
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
cfg['Soundtrack'] = self._selected_soundtrack
|
||||
cfg.commit()
|
||||
|
||||
# Just play whats already playing.. this'll grab it from the
|
||||
# new soundtrack.
|
||||
music.do_play_music(music.music_types[ba.MusicPlayMode.REGULAR])
|
||||
|
||||
def _back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings import audio
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(
|
||||
audio.AudioSettingsWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
|
||||
def _edit_soundtrack_with_sound(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
ba.playsound(ba.getsound('swish'))
|
||||
self._edit_soundtrack()
|
||||
|
||||
def _edit_soundtrack(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
from bastd.ui.soundtrack.edit import SoundtrackEditWindow
|
||||
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
if self._selected_soundtrack is None:
|
||||
return
|
||||
if self._selected_soundtrack == '__default__':
|
||||
ba.playsound(ba.getsound('error'))
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource=self._r + '.cantEditDefaultText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
SoundtrackEditWindow(
|
||||
existing_soundtrack=self._selected_soundtrack
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _get_soundtrack_display_name(self, soundtrack: str) -> ba.Lstr:
|
||||
if soundtrack == '__default__':
|
||||
return ba.Lstr(resource=self._r + '.defaultSoundtrackNameText')
|
||||
return ba.Lstr(value=soundtrack)
|
||||
|
||||
def _refresh(self, select_soundtrack: str | None = None) -> None:
|
||||
from efro.util import asserttype
|
||||
|
||||
self._allow_changing_soundtracks = False
|
||||
old_selection = self._selected_soundtrack
|
||||
|
||||
# If there was no prev selection, look in prefs.
|
||||
if old_selection is None:
|
||||
old_selection = ba.app.config.get('Soundtrack')
|
||||
old_selection_index = self._selected_soundtrack_index
|
||||
|
||||
# Delete old.
|
||||
while self._soundtrack_widgets:
|
||||
self._soundtrack_widgets.pop().delete()
|
||||
|
||||
self._soundtracks = ba.app.config.get('Soundtracks', {})
|
||||
assert self._soundtracks is not None
|
||||
items = list(self._soundtracks.items())
|
||||
items.sort(key=lambda x: asserttype(x[0], str).lower())
|
||||
items = [('__default__', None)] + items # default is always first
|
||||
index = 0
|
||||
for pname, _pval in items:
|
||||
assert pname is not None
|
||||
txtw = ba.textwidget(
|
||||
parent=self._col,
|
||||
size=(self._width - 40, 24),
|
||||
text=self._get_soundtrack_display_name(pname),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
maxwidth=self._width - 110,
|
||||
always_highlight=True,
|
||||
on_select_call=ba.WeakCall(self._select, pname, index),
|
||||
on_activate_call=self._edit_soundtrack_with_sound,
|
||||
selectable=True,
|
||||
)
|
||||
if index == 0:
|
||||
ba.widget(edit=txtw, up_widget=self._back_button)
|
||||
self._soundtrack_widgets.append(txtw)
|
||||
|
||||
# Select this one if the user requested it
|
||||
if select_soundtrack is not None:
|
||||
if pname == select_soundtrack:
|
||||
ba.columnwidget(
|
||||
edit=self._col, selected_child=txtw, visible_child=txtw
|
||||
)
|
||||
else:
|
||||
# Select this one if it was previously selected.
|
||||
# Go by index if there's one.
|
||||
if old_selection_index is not None:
|
||||
if index == old_selection_index:
|
||||
ba.columnwidget(
|
||||
edit=self._col,
|
||||
selected_child=txtw,
|
||||
visible_child=txtw,
|
||||
)
|
||||
else: # Otherwise look by name.
|
||||
if pname == old_selection:
|
||||
ba.columnwidget(
|
||||
edit=self._col,
|
||||
selected_child=txtw,
|
||||
visible_child=txtw,
|
||||
)
|
||||
index += 1
|
||||
|
||||
# Explicitly run select callback on current one and re-enable
|
||||
# callbacks.
|
||||
|
||||
# Eww need to run this in a timer so it happens after our select
|
||||
# callbacks. With a small-enough time sometimes it happens before
|
||||
# anyway. Ew. need a way to just schedule a callable i guess.
|
||||
ba.timer(
|
||||
0.1,
|
||||
ba.WeakCall(self._set_allow_changing),
|
||||
timetype=ba.TimeType.REAL,
|
||||
)
|
||||
|
||||
def _set_allow_changing(self) -> None:
|
||||
self._allow_changing_soundtracks = True
|
||||
assert self._selected_soundtrack is not None
|
||||
assert self._selected_soundtrack_index is not None
|
||||
self._select(self._selected_soundtrack, self._selected_soundtrack_index)
|
||||
|
||||
def _new_soundtrack(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
from bastd.ui.soundtrack.edit import SoundtrackEditWindow
|
||||
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
SoundtrackEditWindow(existing_soundtrack=None)
|
||||
|
||||
def _create_done(self, new_soundtrack: str) -> None:
|
||||
if new_soundtrack is not None:
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
self._refresh(select_soundtrack=new_soundtrack)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._scrollwidget:
|
||||
sel_name = 'Scroll'
|
||||
elif sel == self._new_button:
|
||||
sel_name = 'New'
|
||||
elif sel == self._edit_button:
|
||||
sel_name = 'Edit'
|
||||
elif sel == self._duplicate_button:
|
||||
sel_name = 'Duplicate'
|
||||
elif sel == self._delete_button:
|
||||
sel_name = 'Delete'
|
||||
elif sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
ba.app.ui.window_states[type(self)] = sel_name
|
||||
except Exception:
|
||||
ba.print_exception(f'Error saving state for {self}.')
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
sel_name = ba.app.ui.window_states.get(type(self))
|
||||
if sel_name == 'Scroll':
|
||||
sel = self._scrollwidget
|
||||
elif sel_name == 'New':
|
||||
sel = self._new_button
|
||||
elif sel_name == 'Edit':
|
||||
sel = self._edit_button
|
||||
elif sel_name == 'Duplicate':
|
||||
sel = self._duplicate_button
|
||||
elif sel_name == 'Delete':
|
||||
sel = self._delete_button
|
||||
else:
|
||||
sel = self._scrollwidget
|
||||
ba.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
except Exception:
|
||||
ba.print_exception(f'Error restoring state for {self}.')
|
||||
479
dist/ba_data/python/bastd/ui/soundtrack/edit.py
vendored
Normal file
479
dist/ba_data/python/bastd/ui/soundtrack/edit.py
vendored
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides UI for editing a soundtrack."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import os
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SoundtrackEditWindow(ba.Window):
|
||||
"""Window for editing a soundtrack."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
existing_soundtrack: str | dict[str, Any] | None,
|
||||
transition: str = 'in_right',
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
appconfig = ba.app.config
|
||||
self._r = 'editSoundtrackWindow'
|
||||
self._folder_tex = ba.gettexture('folder')
|
||||
self._file_tex = ba.gettexture('file')
|
||||
uiscale = ba.app.ui.uiscale
|
||||
self._width = 848 if uiscale is ba.UIScale.SMALL else 648
|
||||
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
395
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 450
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 560
|
||||
)
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
transition=transition,
|
||||
scale=(
|
||||
2.08
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.5
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, -48)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 15)
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
cancel_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(38 + x_inset, self._height - 60),
|
||||
size=(160, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='cancelText'),
|
||||
scale=0.8,
|
||||
)
|
||||
save_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - (168 + x_inset), self._height - 60),
|
||||
autoselect=True,
|
||||
size=(160, 60),
|
||||
label=ba.Lstr(resource='saveText'),
|
||||
scale=0.8,
|
||||
)
|
||||
ba.widget(edit=save_button, left_widget=cancel_button)
|
||||
ba.widget(edit=cancel_button, right_widget=save_button)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, self._height - 50),
|
||||
size=(self._width, 25),
|
||||
text=ba.Lstr(
|
||||
resource=self._r
|
||||
+ (
|
||||
'.editSoundtrackText'
|
||||
if existing_soundtrack is not None
|
||||
else '.newSoundtrackText'
|
||||
)
|
||||
),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=280,
|
||||
)
|
||||
v = self._height - 110
|
||||
if 'Soundtracks' not in appconfig:
|
||||
appconfig['Soundtracks'] = {}
|
||||
|
||||
self._soundtrack_name: str | None
|
||||
self._existing_soundtrack_name: str | None
|
||||
if existing_soundtrack is not None:
|
||||
# if they passed just a name, pull info from that soundtrack
|
||||
if isinstance(existing_soundtrack, str):
|
||||
self._soundtrack = copy.deepcopy(
|
||||
appconfig['Soundtracks'][existing_soundtrack]
|
||||
)
|
||||
self._soundtrack_name = existing_soundtrack
|
||||
self._existing_soundtrack_name = existing_soundtrack
|
||||
self._last_edited_song_type = None
|
||||
else:
|
||||
# otherwise they can pass info on an in-progress edit
|
||||
self._soundtrack = existing_soundtrack['soundtrack']
|
||||
self._soundtrack_name = existing_soundtrack['name']
|
||||
self._existing_soundtrack_name = existing_soundtrack[
|
||||
'existing_name'
|
||||
]
|
||||
self._last_edited_song_type = existing_soundtrack[
|
||||
'last_edited_song_type'
|
||||
]
|
||||
else:
|
||||
self._soundtrack_name = None
|
||||
self._existing_soundtrack_name = None
|
||||
self._soundtrack = {}
|
||||
self._last_edited_song_type = None
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=ba.Lstr(resource=self._r + '.nameText'),
|
||||
maxwidth=80,
|
||||
scale=0.8,
|
||||
position=(105 + x_inset, v + 19),
|
||||
color=(0.8, 0.8, 0.8, 0.5),
|
||||
size=(0, 0),
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
# if there's no initial value, find a good initial unused name
|
||||
if existing_soundtrack is None:
|
||||
i = 1
|
||||
st_name_text = ba.Lstr(
|
||||
resource=self._r + '.newSoundtrackNameText'
|
||||
).evaluate()
|
||||
if '${COUNT}' not in st_name_text:
|
||||
# make sure we insert number *somewhere*
|
||||
st_name_text = st_name_text + ' ${COUNT}'
|
||||
while True:
|
||||
self._soundtrack_name = st_name_text.replace('${COUNT}', str(i))
|
||||
if self._soundtrack_name not in appconfig['Soundtracks']:
|
||||
break
|
||||
i += 1
|
||||
|
||||
self._text_field = ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(120 + x_inset, v - 5),
|
||||
size=(self._width - (160 + 2 * x_inset), 43),
|
||||
text=self._soundtrack_name,
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
max_chars=32,
|
||||
autoselect=True,
|
||||
description=ba.Lstr(resource=self._r + '.nameText'),
|
||||
editable=True,
|
||||
padding=4,
|
||||
on_return_press_call=self._do_it_with_sound,
|
||||
)
|
||||
|
||||
scroll_height = self._height - 180
|
||||
self._scrollwidget = scrollwidget = ba.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
position=(40 + x_inset, v - (scroll_height + 10)),
|
||||
size=(self._width - (80 + 2 * x_inset), scroll_height),
|
||||
simple_culling_v=10,
|
||||
claims_left_right=True,
|
||||
claims_tab=True,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
ba.widget(edit=self._text_field, down_widget=self._scrollwidget)
|
||||
self._col = ba.columnwidget(
|
||||
parent=scrollwidget,
|
||||
claims_left_right=True,
|
||||
claims_tab=True,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
|
||||
self._song_type_buttons: dict[str, ba.Widget] = {}
|
||||
self._refresh()
|
||||
ba.buttonwidget(edit=cancel_button, on_activate_call=self._cancel)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=cancel_button)
|
||||
ba.buttonwidget(edit=save_button, on_activate_call=self._do_it)
|
||||
ba.containerwidget(edit=self._root_widget, start_button=save_button)
|
||||
ba.widget(edit=self._text_field, up_widget=cancel_button)
|
||||
ba.widget(edit=cancel_button, down_widget=self._text_field)
|
||||
|
||||
def _refresh(self) -> None:
|
||||
for widget in self._col.get_children():
|
||||
widget.delete()
|
||||
|
||||
types = [
|
||||
'Menu',
|
||||
'CharSelect',
|
||||
'ToTheDeath',
|
||||
'Onslaught',
|
||||
'Keep Away',
|
||||
'Race',
|
||||
'Epic Race',
|
||||
'ForwardMarch',
|
||||
'FlagCatcher',
|
||||
'Survival',
|
||||
'Epic',
|
||||
'Hockey',
|
||||
'Football',
|
||||
'Flying',
|
||||
'Scary',
|
||||
'Marching',
|
||||
'GrandRomp',
|
||||
'Chosen One',
|
||||
'Scores',
|
||||
'Victory',
|
||||
]
|
||||
|
||||
# FIXME: We should probably convert this to use translations.
|
||||
type_names_translated = ba.app.lang.get_resource('soundtrackTypeNames')
|
||||
prev_type_button: ba.Widget | None = None
|
||||
prev_test_button: ba.Widget | None = None
|
||||
|
||||
for index, song_type in enumerate(types):
|
||||
row = ba.rowwidget(
|
||||
parent=self._col,
|
||||
size=(self._width - 40, 40),
|
||||
claims_left_right=True,
|
||||
claims_tab=True,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
type_name = type_names_translated.get(song_type, song_type)
|
||||
ba.textwidget(
|
||||
parent=row,
|
||||
size=(230, 25),
|
||||
always_highlight=True,
|
||||
text=type_name,
|
||||
scale=0.7,
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
maxwidth=190,
|
||||
)
|
||||
|
||||
if song_type in self._soundtrack:
|
||||
entry = self._soundtrack[song_type]
|
||||
else:
|
||||
entry = None
|
||||
|
||||
if entry is not None:
|
||||
# Make sure they don't muck with this after it gets to us.
|
||||
entry = copy.deepcopy(entry)
|
||||
|
||||
icon_type = self._get_entry_button_display_icon_type(entry)
|
||||
self._song_type_buttons[song_type] = btn = ba.buttonwidget(
|
||||
parent=row,
|
||||
size=(230, 32),
|
||||
label=self._get_entry_button_display_name(entry),
|
||||
text_scale=0.6,
|
||||
on_activate_call=ba.Call(
|
||||
self._get_entry, song_type, entry, type_name
|
||||
),
|
||||
icon=(
|
||||
self._file_tex
|
||||
if icon_type == 'file'
|
||||
else self._folder_tex
|
||||
if icon_type == 'folder'
|
||||
else None
|
||||
),
|
||||
icon_color=(1.1, 0.8, 0.2)
|
||||
if icon_type == 'folder'
|
||||
else (1, 1, 1),
|
||||
left_widget=self._text_field,
|
||||
iconscale=0.7,
|
||||
autoselect=True,
|
||||
up_widget=prev_type_button,
|
||||
)
|
||||
if index == 0:
|
||||
ba.widget(edit=btn, up_widget=self._text_field)
|
||||
ba.widget(edit=btn, down_widget=btn)
|
||||
|
||||
if (
|
||||
self._last_edited_song_type is not None
|
||||
and song_type == self._last_edited_song_type
|
||||
):
|
||||
ba.containerwidget(
|
||||
edit=row, selected_child=btn, visible_child=btn
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._col, selected_child=row, visible_child=row
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._scrollwidget,
|
||||
selected_child=self._col,
|
||||
visible_child=self._col,
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget,
|
||||
selected_child=self._scrollwidget,
|
||||
visible_child=self._scrollwidget,
|
||||
)
|
||||
|
||||
if prev_type_button is not None:
|
||||
ba.widget(edit=prev_type_button, down_widget=btn)
|
||||
prev_type_button = btn
|
||||
ba.textwidget(parent=row, size=(10, 32), text='') # spacing
|
||||
btn = ba.buttonwidget(
|
||||
parent=row,
|
||||
size=(50, 32),
|
||||
label=ba.Lstr(resource=self._r + '.testText'),
|
||||
text_scale=0.6,
|
||||
on_activate_call=ba.Call(self._test, ba.MusicType(song_type)),
|
||||
up_widget=prev_test_button
|
||||
if prev_test_button is not None
|
||||
else self._text_field,
|
||||
)
|
||||
if prev_test_button is not None:
|
||||
ba.widget(edit=prev_test_button, down_widget=btn)
|
||||
ba.widget(edit=btn, down_widget=btn, right_widget=btn)
|
||||
prev_test_button = btn
|
||||
|
||||
@classmethod
|
||||
def _restore_editor(
|
||||
cls, state: dict[str, Any], musictype: str, entry: Any
|
||||
) -> None:
|
||||
music = ba.app.music
|
||||
|
||||
# Apply the change and recreate the window.
|
||||
soundtrack = state['soundtrack']
|
||||
existing_entry = (
|
||||
None if musictype not in soundtrack else soundtrack[musictype]
|
||||
)
|
||||
if existing_entry != entry:
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
|
||||
# Make sure this doesn't get mucked with after we get it.
|
||||
if entry is not None:
|
||||
entry = copy.deepcopy(entry)
|
||||
|
||||
entry_type = music.get_soundtrack_entry_type(entry)
|
||||
if entry_type == 'default':
|
||||
# For 'default' entries simply exclude them from the list.
|
||||
if musictype in soundtrack:
|
||||
del soundtrack[musictype]
|
||||
else:
|
||||
soundtrack[musictype] = entry
|
||||
|
||||
ba.app.ui.set_main_menu_window(
|
||||
cls(state, transition='in_left').get_root_widget()
|
||||
)
|
||||
|
||||
def _get_entry(
|
||||
self, song_type: str, entry: Any, selection_target_name: str
|
||||
) -> None:
|
||||
music = ba.app.music
|
||||
if selection_target_name != '':
|
||||
selection_target_name = "'" + selection_target_name + "'"
|
||||
state = {
|
||||
'name': self._soundtrack_name,
|
||||
'existing_name': self._existing_soundtrack_name,
|
||||
'soundtrack': self._soundtrack,
|
||||
'last_edited_song_type': song_type,
|
||||
}
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
music.get_music_player()
|
||||
.select_entry(
|
||||
ba.Call(self._restore_editor, state, song_type),
|
||||
entry,
|
||||
selection_target_name,
|
||||
)
|
||||
.get_root_widget()
|
||||
)
|
||||
|
||||
def _test(self, song_type: ba.MusicType) -> None:
|
||||
music = ba.app.music
|
||||
|
||||
# Warn if volume is zero.
|
||||
if ba.app.config.resolve('Music Volume') < 0.01:
|
||||
ba.playsound(ba.getsound('error'))
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource=self._r + '.musicVolumeZeroWarning'),
|
||||
color=(1, 0.5, 0),
|
||||
)
|
||||
music.set_music_play_mode(ba.MusicPlayMode.TEST)
|
||||
music.do_play_music(
|
||||
song_type,
|
||||
mode=ba.MusicPlayMode.TEST,
|
||||
testsoundtrack=self._soundtrack,
|
||||
)
|
||||
|
||||
def _get_entry_button_display_name(self, entry: Any) -> str | ba.Lstr:
|
||||
music = ba.app.music
|
||||
etype = music.get_soundtrack_entry_type(entry)
|
||||
ename: str | ba.Lstr
|
||||
if etype == 'default':
|
||||
ename = ba.Lstr(resource=self._r + '.defaultGameMusicText')
|
||||
elif etype in ('musicFile', 'musicFolder'):
|
||||
ename = os.path.basename(music.get_soundtrack_entry_name(entry))
|
||||
else:
|
||||
ename = music.get_soundtrack_entry_name(entry)
|
||||
return ename
|
||||
|
||||
def _get_entry_button_display_icon_type(self, entry: Any) -> str | None:
|
||||
music = ba.app.music
|
||||
etype = music.get_soundtrack_entry_type(entry)
|
||||
if etype == 'musicFile':
|
||||
return 'file'
|
||||
if etype == 'musicFolder':
|
||||
return 'folder'
|
||||
return None
|
||||
|
||||
def _cancel(self) -> None:
|
||||
from bastd.ui.soundtrack import browser as stb
|
||||
|
||||
music = ba.app.music
|
||||
|
||||
# Resets music back to normal.
|
||||
music.set_music_play_mode(ba.MusicPlayMode.REGULAR)
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
|
||||
def _do_it(self) -> None:
|
||||
from bastd.ui.soundtrack import browser as stb
|
||||
|
||||
music = ba.app.music
|
||||
cfg = ba.app.config
|
||||
new_name = cast(str, ba.textwidget(query=self._text_field))
|
||||
if new_name != self._soundtrack_name and new_name in cfg['Soundtracks']:
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')
|
||||
)
|
||||
ba.playsound(ba.getsound('error'))
|
||||
return
|
||||
if not new_name:
|
||||
ba.playsound(ba.getsound('error'))
|
||||
return
|
||||
if (
|
||||
new_name
|
||||
== ba.Lstr(
|
||||
resource=self._r + '.defaultSoundtrackNameText'
|
||||
).evaluate()
|
||||
):
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource=self._r + '.cantOverwriteDefaultText')
|
||||
)
|
||||
ba.playsound(ba.getsound('error'))
|
||||
return
|
||||
|
||||
# Make sure config exists.
|
||||
if 'Soundtracks' not in cfg:
|
||||
cfg['Soundtracks'] = {}
|
||||
|
||||
# If we had an old one, delete it.
|
||||
if (
|
||||
self._existing_soundtrack_name is not None
|
||||
and self._existing_soundtrack_name in cfg['Soundtracks']
|
||||
):
|
||||
del cfg['Soundtracks'][self._existing_soundtrack_name]
|
||||
cfg['Soundtracks'][new_name] = self._soundtrack
|
||||
cfg['Soundtrack'] = new_name
|
||||
|
||||
cfg.commit()
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
|
||||
# Resets music back to normal.
|
||||
music.set_music_play_mode(ba.MusicPlayMode.REGULAR, force_restart=True)
|
||||
|
||||
ba.app.ui.set_main_menu_window(
|
||||
stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
|
||||
def _do_it_with_sound(self) -> None:
|
||||
ba.playsound(ba.getsound('swish'))
|
||||
self._do_it()
|
||||
237
dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py
vendored
Normal file
237
dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides UI for selecting soundtrack entry types."""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
class SoundtrackEntryTypeSelectWindow(ba.Window):
|
||||
"""Window for selecting a soundtrack entry type."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Any], Any],
|
||||
current_entry: Any,
|
||||
selection_target_name: str,
|
||||
transition: str = 'in_right',
|
||||
):
|
||||
music = ba.app.music
|
||||
self._r = 'editSoundtrackWindow'
|
||||
|
||||
self._callback = callback
|
||||
self._current_entry = copy.deepcopy(current_entry)
|
||||
|
||||
self._width = 580
|
||||
self._height = 220
|
||||
spacing = 80
|
||||
|
||||
# FIXME: Generalize this so new custom soundtrack types can add
|
||||
# themselves here.
|
||||
do_default = True
|
||||
do_mac_music_app_playlist = music.supports_soundtrack_entry_type(
|
||||
'iTunesPlaylist'
|
||||
)
|
||||
do_music_file = music.supports_soundtrack_entry_type('musicFile')
|
||||
do_music_folder = music.supports_soundtrack_entry_type('musicFolder')
|
||||
|
||||
if do_mac_music_app_playlist:
|
||||
self._height += spacing
|
||||
if do_music_file:
|
||||
self._height += spacing
|
||||
if do_music_folder:
|
||||
self._height += spacing
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
|
||||
# NOTE: When something is selected, we close our UI and kick off
|
||||
# another window which then calls us back when its done, so the
|
||||
# standard UI-cleanup-check complains that something is holding on
|
||||
# to our instance after its ui is gone. Should restructure in a
|
||||
# cleaner way, but just disabling that check for now.
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
transition=transition,
|
||||
scale=(
|
||||
1.7
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.4
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
),
|
||||
cleanupcheck=False,
|
||||
)
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(35, self._height - 65),
|
||||
size=(160, 60),
|
||||
scale=0.8,
|
||||
text_scale=1.2,
|
||||
label=ba.Lstr(resource='cancelText'),
|
||||
on_activate_call=self._on_cancel_press,
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 32),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.selectASourceText'),
|
||||
color=ba.app.ui.title_color,
|
||||
maxwidth=230,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 56),
|
||||
size=(0, 0),
|
||||
text=selection_target_name,
|
||||
color=ba.app.ui.infotextcolor,
|
||||
scale=0.7,
|
||||
maxwidth=230,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
v = self._height - 155
|
||||
|
||||
current_entry_type = music.get_soundtrack_entry_type(current_entry)
|
||||
|
||||
if do_default:
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
size=(self._width - 100, 60),
|
||||
position=(50, v),
|
||||
label=ba.Lstr(resource=self._r + '.useDefaultGameMusicText'),
|
||||
on_activate_call=self._on_default_press,
|
||||
)
|
||||
if current_entry_type == 'default':
|
||||
ba.containerwidget(edit=self._root_widget, selected_child=btn)
|
||||
v -= spacing
|
||||
|
||||
if do_mac_music_app_playlist:
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
size=(self._width - 100, 60),
|
||||
position=(50, v),
|
||||
label=ba.Lstr(resource=self._r + '.useITunesPlaylistText'),
|
||||
on_activate_call=self._on_mac_music_app_playlist_press,
|
||||
icon=None,
|
||||
)
|
||||
if current_entry_type == 'iTunesPlaylist':
|
||||
ba.containerwidget(edit=self._root_widget, selected_child=btn)
|
||||
v -= spacing
|
||||
|
||||
if do_music_file:
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
size=(self._width - 100, 60),
|
||||
position=(50, v),
|
||||
label=ba.Lstr(resource=self._r + '.useMusicFileText'),
|
||||
on_activate_call=self._on_music_file_press,
|
||||
icon=ba.gettexture('file'),
|
||||
)
|
||||
if current_entry_type == 'musicFile':
|
||||
ba.containerwidget(edit=self._root_widget, selected_child=btn)
|
||||
v -= spacing
|
||||
|
||||
if do_music_folder:
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
size=(self._width - 100, 60),
|
||||
position=(50, v),
|
||||
label=ba.Lstr(resource=self._r + '.useMusicFolderText'),
|
||||
on_activate_call=self._on_music_folder_press,
|
||||
icon=ba.gettexture('folder'),
|
||||
icon_color=(1.1, 0.8, 0.2),
|
||||
)
|
||||
if current_entry_type == 'musicFolder':
|
||||
ba.containerwidget(edit=self._root_widget, selected_child=btn)
|
||||
v -= spacing
|
||||
|
||||
def _on_mac_music_app_playlist_press(self) -> None:
|
||||
music = ba.app.music
|
||||
from bastd.ui.soundtrack.macmusicapp import (
|
||||
MacMusicAppPlaylistSelectWindow,
|
||||
)
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
|
||||
current_playlist_entry: str | None
|
||||
if (
|
||||
music.get_soundtrack_entry_type(self._current_entry)
|
||||
== 'iTunesPlaylist'
|
||||
):
|
||||
current_playlist_entry = music.get_soundtrack_entry_name(
|
||||
self._current_entry
|
||||
)
|
||||
else:
|
||||
current_playlist_entry = None
|
||||
ba.app.ui.set_main_menu_window(
|
||||
MacMusicAppPlaylistSelectWindow(
|
||||
self._callback, current_playlist_entry, self._current_entry
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _on_music_file_press(self) -> None:
|
||||
from ba.osmusic import OSMusicPlayer
|
||||
from bastd.ui.fileselector import FileSelectorWindow
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
base_path = ba.internal.android_get_external_files_dir()
|
||||
ba.app.ui.set_main_menu_window(
|
||||
FileSelectorWindow(
|
||||
base_path,
|
||||
callback=self._music_file_selector_cb,
|
||||
show_base_path=False,
|
||||
valid_file_extensions=(
|
||||
OSMusicPlayer.get_valid_music_file_extensions()
|
||||
),
|
||||
allow_folders=False,
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _on_music_folder_press(self) -> None:
|
||||
from bastd.ui.fileselector import FileSelectorWindow
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
base_path = ba.internal.android_get_external_files_dir()
|
||||
ba.app.ui.set_main_menu_window(
|
||||
FileSelectorWindow(
|
||||
base_path,
|
||||
callback=self._music_folder_selector_cb,
|
||||
show_base_path=False,
|
||||
valid_file_extensions=[],
|
||||
allow_folders=True,
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _music_file_selector_cb(self, result: str | None) -> None:
|
||||
if result is None:
|
||||
self._callback(self._current_entry)
|
||||
else:
|
||||
self._callback({'type': 'musicFile', 'name': result})
|
||||
|
||||
def _music_folder_selector_cb(self, result: str | None) -> None:
|
||||
if result is None:
|
||||
self._callback(self._current_entry)
|
||||
else:
|
||||
self._callback({'type': 'musicFolder', 'name': result})
|
||||
|
||||
def _on_default_press(self) -> None:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
self._callback(None)
|
||||
|
||||
def _on_cancel_press(self) -> None:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
self._callback(self._current_entry)
|
||||
118
dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py
vendored
Normal file
118
dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality related to using the macOS Music app for soundtracks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
class MacMusicAppPlaylistSelectWindow(ba.Window):
|
||||
"""Window for selecting an iTunes playlist."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Any], Any],
|
||||
existing_playlist: str | None,
|
||||
existing_entry: Any,
|
||||
):
|
||||
from ba.macmusicapp import MacMusicAppMusicPlayer
|
||||
|
||||
self._r = 'editSoundtrackWindow'
|
||||
self._callback = callback
|
||||
self._existing_playlist = existing_playlist
|
||||
self._existing_entry = copy.deepcopy(existing_entry)
|
||||
self._width = 520.0
|
||||
self._height = 520.0
|
||||
self._spacing = 45.0
|
||||
v = self._height - 90.0
|
||||
v -= self._spacing * 1.0
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height), transition='in_right'
|
||||
)
|
||||
)
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(35, self._height - 65),
|
||||
size=(130, 50),
|
||||
label=ba.Lstr(resource='cancelText'),
|
||||
on_activate_call=self._back,
|
||||
autoselect=True,
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(20, self._height - 54),
|
||||
size=(self._width, 25),
|
||||
text=ba.Lstr(resource=self._r + '.selectAPlaylistText'),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=200,
|
||||
)
|
||||
self._scrollwidget = ba.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
position=(40, v - 340),
|
||||
size=(self._width - 80, 400),
|
||||
claims_tab=True,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
|
||||
self._column = ba.columnwidget(
|
||||
parent=self._scrollwidget,
|
||||
claims_tab=True,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._column,
|
||||
size=(self._width - 80, 22),
|
||||
text=ba.Lstr(resource=self._r + '.fetchingITunesText'),
|
||||
color=(0.6, 0.9, 0.6, 1.0),
|
||||
scale=0.8,
|
||||
)
|
||||
musicplayer = ba.app.music.get_music_player()
|
||||
assert isinstance(musicplayer, MacMusicAppMusicPlayer)
|
||||
musicplayer.get_playlists(self._playlists_cb)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
|
||||
def _playlists_cb(self, playlists: list[str]) -> None:
|
||||
if self._column:
|
||||
for widget in self._column.get_children():
|
||||
widget.delete()
|
||||
for i, playlist in enumerate(playlists):
|
||||
txt = ba.textwidget(
|
||||
parent=self._column,
|
||||
size=(self._width - 80, 30),
|
||||
text=playlist,
|
||||
v_align='center',
|
||||
maxwidth=self._width - 110,
|
||||
selectable=True,
|
||||
on_activate_call=ba.Call(self._sel, playlist),
|
||||
click_activate=True,
|
||||
)
|
||||
ba.widget(edit=txt, show_buffer_top=40, show_buffer_bottom=40)
|
||||
if playlist == self._existing_playlist:
|
||||
ba.columnwidget(
|
||||
edit=self._column, selected_child=txt, visible_child=txt
|
||||
)
|
||||
if i == len(playlists) - 1:
|
||||
ba.widget(edit=txt, down_widget=txt)
|
||||
|
||||
def _sel(self, selection: str) -> None:
|
||||
if self._root_widget:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
self._callback({'type': 'iTunesPlaylist', 'name': selection})
|
||||
|
||||
def _back(self) -> None:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
self._callback(self._existing_entry)
|
||||
Loading…
Add table
Add a link
Reference in a new issue