# Released under the MIT License. See LICENSE for details.
#
"""Defines a controller for wrangling playlist edit UIs."""
from __future__ import annotations
import copy
from typing import TYPE_CHECKING
import bascenev1 as bs
import bauiv1 as bui
if TYPE_CHECKING:
from typing import Any, Callable
[docs]
class PlaylistEditController:
"""Coordinates various UIs involved in playlist editing."""
def __init__(
self,
sessiontype: type[bs.Session],
from_window: bui.MainWindow,
*,
existing_playlist_name: str | None = None,
playlist: list[dict[str, Any]] | None = None,
playlist_name: str | None = None,
):
from bascenev1 import filter_playlist
from bauiv1lib.playlist import PlaylistTypeVars
from bauiv1lib.playlist.edit import PlaylistEditWindow
appconfig = bui.app.config
# Since we may be showing our map list momentarily,
# lets go ahead and preload all map preview textures.
if bui.app.classic is not None:
bui.app.classic.preload_map_preview_media()
self._sessiontype = sessiontype
self._editing_game = False
self._editing_game_type: type[bs.GameActivity] | None = None
self._pvars = PlaylistTypeVars(sessiontype)
self._existing_playlist_name = existing_playlist_name
self._config_name_full = self._pvars.config_name + ' Playlists'
self._pre_game_add_state: bui.MainWindowState | None = None
self._pre_game_edit_state: bui.MainWindowState | None = None
# Make sure config exists.
if self._config_name_full not in appconfig:
appconfig[self._config_name_full] = {}
self._selected_index = 0
if existing_playlist_name:
self._name = existing_playlist_name
# Filter out invalid games.
self._playlist = filter_playlist(
appconfig[self._pvars.config_name + ' Playlists'][
existing_playlist_name
],
sessiontype=sessiontype,
remove_unowned=False,
name=existing_playlist_name,
)
self._edit_ui_selection = None
else:
if playlist is not None:
self._playlist = playlist
else:
self._playlist = []
if playlist_name is not None:
self._name = playlist_name
else:
# Find a good unused name.
i = 1
while True:
self._name = (
self._pvars.default_new_list_name.evaluate()
+ ((' ' + str(i)) if i > 1 else '')
)
if (
self._name
not in appconfig[self._pvars.config_name + ' Playlists']
):
break
i += 1
# Also we want it to start with 'add' highlighted since its empty
# and that's all they can do.
self._edit_ui_selection = 'add_button'
editwindow = PlaylistEditWindow(editcontroller=self)
from_window.main_window_replace(editwindow)
# Once we've set our start window, store the back state. We'll
# skip back to there once we're fully done.
self._back_state = editwindow.main_window_back_state
[docs]
def get_config_name(self) -> str:
"""(internal)"""
return self._pvars.config_name
[docs]
def get_existing_playlist_name(self) -> str | None:
"""(internal)"""
return self._existing_playlist_name
[docs]
def get_edit_ui_selection(self) -> str | None:
"""(internal)"""
return self._edit_ui_selection
[docs]
def set_edit_ui_selection(self, selection: str) -> None:
"""(internal)"""
self._edit_ui_selection = selection
[docs]
def getname(self) -> str:
"""(internal)"""
return self._name
[docs]
def setname(self, name: str) -> None:
"""(internal)"""
self._name = name
[docs]
def get_playlist(self) -> list[dict[str, Any]]:
"""Return the current state of the edited playlist."""
return copy.deepcopy(self._playlist)
[docs]
def set_playlist(self, playlist: list[dict[str, Any]]) -> None:
"""Set the playlist contents."""
self._playlist = copy.deepcopy(playlist)
[docs]
def get_session_type(self) -> type[bs.Session]:
"""Return the bascenev1.Session type for this edit-session."""
return self._sessiontype
[docs]
def get_selected_index(self) -> int:
"""Return the index of the selected playlist."""
return self._selected_index
[docs]
def get_default_list_name(self) -> bui.Lstr:
"""(internal)"""
return self._pvars.default_list_name
[docs]
def set_selected_index(self, index: int) -> None:
"""Sets the selected playlist index."""
self._selected_index = index
[docs]
def add_game_pressed(self, from_window: bui.MainWindow) -> None:
"""(internal)"""
from bauiv1lib.playlist.addgame import PlaylistAddGameWindow
# assert bui.app.classic is not None
# No op if we're not in control.
if not from_window.main_window_has_control():
return
addwindow = PlaylistAddGameWindow(editcontroller=self)
from_window.main_window_replace(addwindow)
# Once we're there, store the back state. We'll use that to jump
# back to our current location once the edit is done.
assert self._pre_game_add_state is None
self._pre_game_add_state = addwindow.main_window_back_state
[docs]
def edit_game_pressed(self, from_window: bui.MainWindow) -> None:
"""Should be called by supplemental UIs when a game is to be edited."""
if not self._playlist:
return
self._show_edit_ui(
gametype=bui.getclass(
self._playlist[self._selected_index]['type'],
subclassof=bs.GameActivity,
),
settings=self._playlist[self._selected_index],
from_window=from_window,
)
def _show_edit_ui(
self,
gametype: type[bs.GameActivity],
settings: dict[str, Any] | None,
from_window: bui.MainWindow,
) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
if not from_window.main_window_has_control():
return
self._editing_game = settings is not None
self._editing_game_type = gametype
assert self._sessiontype is not None
# Jump into an edit window.
editwindow = PlaylistEditGameWindow(
gametype,
self._sessiontype,
copy.deepcopy(settings),
completion_call=self._edit_game_done,
)
from_window.main_window_replace(editwindow)
# Once we're there, store the back state. We'll use that to jump
# back to our current location once the edit is done.
assert self._pre_game_edit_state is None
self._pre_game_edit_state = editwindow.main_window_back_state
[docs]
def add_game_type_selected(
self, gametype: type[bs.GameActivity], from_window: bui.MainWindow
) -> None:
"""(internal)"""
self._show_edit_ui(
gametype=gametype, settings=None, from_window=from_window
)
def _edit_game_done(
self, config: dict[str, Any] | None, from_window: bui.MainWindow
) -> None:
# No-op if provided window isn't in charge.
if not from_window.main_window_has_control():
return
assert bui.app.classic is not None
if config is None:
bui.getsound('powerdown01').play()
else:
# Make sure type is in there.
assert self._editing_game_type is not None
config['type'] = bui.get_type_name(self._editing_game_type)
if self._editing_game:
self._playlist[self._selected_index] = copy.deepcopy(config)
else:
# Add a new entry to the playlist.
insert_index = min(
len(self._playlist), self._selected_index + 1
)
self._playlist.insert(insert_index, copy.deepcopy(config))
self._selected_index = insert_index
bui.getsound('gunCocking').play()
# If we're adding, jump to before the add started.
# Otherwise jump to before the edit started.
assert (
self._pre_game_edit_state is not None
or self._pre_game_add_state is not None
)
if self._pre_game_add_state is not None:
from_window.main_window_back_state = self._pre_game_add_state
elif self._pre_game_edit_state is not None:
from_window.main_window_back_state = self._pre_game_edit_state
from_window.main_window_back()
self._pre_game_edit_state = None
self._pre_game_add_state = None
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum