Work on type-safe TabRow class

This commit is contained in:
Eric Froemling 2020-10-20 13:59:06 -07:00
parent eba70f0c81
commit 981375f51e
9 changed files with 160 additions and 77 deletions

View File

@ -3932,24 +3932,24 @@
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450",
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/dd/d1/e12c5331256ecffcd7684d6d2963",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c5/35/8d811eec1a47f4e70d4b27eb3bf5",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6f/43/d0f61fb34a76e11b9ebd4334038d",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a4/56/614e09d8ab86355f0d65c1ce76fe",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f7/c5/f695953295c2d79dfb01555aea89",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7b/19/ff1bba3a148b6e0f0823d7d7c7bf",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f7/dd/d5e4d872192060d46821d6911c13",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/64/58/962c2e707ee66d310848f375796a",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/db/58/65c487facba1de6ba8ce65f19f01",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/40/cb/afd71350fdc9827ca99baab73001",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/f2/05/e9252b68a96ee418da35dd4d2530",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/bb/a2/e1f5b3f561a08bb09cc3ffb2492f",
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/81/be/dd6f786f096520a5450b96b0efd9",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/b0/14/defa61c54179e1810f8308cac55c",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/89/2d/9c41cd7bba801f79906a397f9b23",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/78/bb/4656448316b4b5ff7bc348ede9f2",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c0/e0/54ff7942beddb65315194b79da09",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/5b/52/1771901e2344bad49238986c80b2",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2d/d7/e1ffb234f71e715587456fc7cfd8",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/99/88/2ee8c18b63f64425516050c8a304",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ac/a2/75427a8ecc1f19d402b58f9014e6",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d7/d4/4640f90f0ae2d2b98b8fbf099c2f",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/95/94/a110e7c8d338d5a6cefe2e975e8b",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/51/26/638d82bbf9acbfaffd8cfe780671",
"build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/66/c9/3b04209f599dea8b8ca4be7d3404",
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0d/3b/b7b46c3131cff8a40dfaa001af38",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/57/40/0c1d88af3ce14e0f8870ab9ac7ad",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8a/72/02b4eddf662001f05f98288d4ad4",
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fc/bc/51529aac7531d1a62cf13eb79153",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ea/7b/de9ce5284627cc77b2fffc354a66",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/96/00/78b64146e33ec35dcde9c278328a",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6d/25/0e5918fa1eb7285538d14051761c"
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ac/e6/0e2476b60bfa7d3af4e9a2a6db1f",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f3/81/218f5899da12bcaf1128175e7ef0",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/84/14/923a2f78920255adffe574b613d6",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4e/f0/8e6fdd837a08e6daae5bd9e743dc"
}

View File

@ -29,8 +29,8 @@
<w>achname</w>
<w>achs</w>
<w>acinstance</w>
<w>ack</w>
<w>ack'ed</w>
<w>ack</w>
<w>acked</w>
<w>acks</w>
<w>acnt</w>
@ -152,8 +152,8 @@
<w>bacommon</w>
<w>badguy</w>
<w>bafoundation</w>
<w>ballistica</w>
<w>ballistica's</w>
<w>ballistica</w>
<w>ballisticacore</w>
<w>ballisticacorecb</w>
<w>bamaster</w>
@ -800,8 +800,8 @@
<w>gamedata</w>
<w>gameinstance</w>
<w>gamemap</w>
<w>gamepad</w>
<w>gamepad's</w>
<w>gamepad</w>
<w>gamepadadvanced</w>
<w>gamepads</w>
<w>gamepadselect</w>
@ -1189,8 +1189,8 @@
<w>lsqlite</w>
<w>lssl</w>
<w>lstart</w>
<w>lstr</w>
<w>lstr's</w>
<w>lstr</w>
<w>lstrs</w>
<w>lsval</w>
<w>ltex</w>
@ -1823,8 +1823,8 @@
<w>sessionname</w>
<w>sessionplayer</w>
<w>sessionplayers</w>
<w>sessionteam</w>
<w>sessionteam's</w>
<w>sessionteam</w>
<w>sessionteams</w>
<w>sessiontype</w>
<w>setactivity</w>
@ -2009,6 +2009,7 @@
<w>sysconfigdata</w>
<w>sysctl</w>
<w>syslogmodule</w>
<w>tabdefs</w>
<w>tabval</w>
<w>tagargs</w>
<w>tagversion</w>
@ -2156,8 +2157,8 @@
<w>txtw</w>
<w>typeargs</w>
<w>typecheck</w>
<w>typechecker</w>
<w>typechecker's</w>
<w>typechecker</w>
<w>typedval</w>
<w>typeshed</w>
<w>typestr</w>

View File

@ -7,6 +7,14 @@ import random
from typing import TYPE_CHECKING
import _ba
from ba._music import MusicSubsystem
from ba._language import LanguageSubsystem
from ba._ui import UISubsystem
from ba._achievement import AchievementSubsystem
from ba._plugin import PluginSubsystem
from ba._account import AccountSubsystem
from ba._meta import MetadataSubsystem
from ba._ads import AdsSubsystem
if TYPE_CHECKING:
import ba
@ -165,14 +173,6 @@ class App:
the single shared instance.
"""
# pylint: disable=too-many-statements
from ba._music import MusicSubsystem
from ba._language import LanguageSubsystem
from ba._ui import UISubsystem
from ba._achievement import AchievementSubsystem
from ba._plugin import PluginSubsystem
from ba._account import AccountSubsystem
from ba._meta import MetadataSubsystem
from ba._ads import AdsSubsystem
# Config.
self.config_file_healthy = False

View File

@ -175,6 +175,7 @@ def print_live_object_warnings(when: Any,
from ba._session import Session
from ba._actor import Actor
from ba._activity import Activity
sessions: List[ba.Session] = []
activities: List[ba.Activity] = []
actors: List[ba.Actor] = []

View File

@ -4,7 +4,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from dataclasses import dataclass
from typing import TYPE_CHECKING, TypeVar, Generic
import ba
@ -12,6 +13,70 @@ if TYPE_CHECKING:
from typing import Any, Callable, Dict, Tuple, List, Sequence, Optional
@dataclass
class Tab:
"""Info for an individual tab in a TabRow"""
button: ba.Widget
position: Tuple[float, float]
size: Tuple[float, float]
T = TypeVar('T')
class TabRow(Generic[T]):
"""Encapsulates a row of tab-styled buttons.
Tabs are indexed by id which is an arbitrary user-provided type.
"""
def __init__(self,
parent: ba.Widget,
tabdefs: List[Tuple[T, ba.Lstr]],
pos: Tuple[float, float],
size: Tuple[float, float],
on_select_call: Callable[[T], None] = None) -> None:
if not tabdefs:
raise ValueError('At least one tab def is required')
self.tabs: Dict[T, Tab] = {}
tab_pos_v = pos[1]
tab_button_width = float(size[0]) / len(tabdefs)
tab_spacing = (250.0 - tab_button_width) * 0.06
h = pos[0]
for tab_id, tab_label in tabdefs:
pos = (h + tab_spacing * 0.5, tab_pos_v)
size = (tab_button_width - tab_spacing, 50.0)
btn = ba.buttonwidget(parent=parent,
position=pos,
autoselect=True,
button_type='tab',
size=size,
label=tab_label,
enable_sound=False,
on_activate_call=ba.Call(
self._tick_and_call, on_select_call,
tab_id))
h += tab_button_width
self.tabs[tab_id] = Tab(button=btn, position=pos, size=size)
def update_appearance(self, selected_tab_id: T) -> None:
"""Update appearances to make the provided tab appear selected."""
for tab_id, tab in self.tabs.items():
if tab_id == selected_tab_id:
ba.buttonwidget(edit=tab.button,
color=(0.5, 0.4, 0.93),
textcolor=(0.85, 0.75, 0.95)) # lit
else:
ba.buttonwidget(edit=tab.button,
color=(0.52, 0.48, 0.63),
textcolor=(0.65, 0.6, 0.7)) # unlit
def _tick_and_call(self, call: Optional[Callable], arg: Any) -> None:
ba.playsound(ba.getsound('click01'))
if call is not None:
call(arg)
def create_tab_buttons(parent_widget: ba.Widget,
tabs: List[Tuple[str, ba.Lstr]],
pos: Sequence[float],
@ -25,7 +90,7 @@ def create_tab_buttons(parent_widget: ba.Widget,
tab_buttons_indexed = []
tab_button_width = float(size[0]) / len(tabs)
# add a bit more visual spacing as our buttons get narrower
# Add a bit more visual spacing as our buttons get narrower.
tab_spacing = (250.0 - tab_button_width) * 0.06
positions = []
sizes = []

View File

@ -5,6 +5,7 @@
from __future__ import annotations
import os
from enum import Enum
from typing import TYPE_CHECKING, cast
import _ba
@ -14,6 +15,12 @@ if TYPE_CHECKING:
from typing import Any, Optional, Tuple, Dict
class TabID(Enum):
"""Our available tab types."""
MY_REPLAYS = 'my_replays'
TEST_TAB = 'test_tab'
class WatchWindow(ba.Window):
"""Window for watching replays."""
@ -22,7 +29,7 @@ class WatchWindow(ba.Window):
origin_widget: ba.Widget = None):
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
from bastd.ui import tabs
from bastd.ui.tabs import TabRow
ba.set_analytics_screen('Watch Window')
scale_origin: Optional[Tuple[float, float]]
if origin_widget is not None:
@ -47,7 +54,7 @@ class WatchWindow(ba.Window):
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
self._height = (578 if uiscale is ba.UIScale.SMALL else
670 if uiscale is ba.UIScale.MEDIUM else 800)
self._current_tab: Optional[str] = None
self._current_tab: Optional[TabID] = None
extra_top = 20 if uiscale is ba.UIScale.SMALL else 0
super().__init__(root_widget=ba.containerwidget(
@ -90,32 +97,35 @@ class WatchWindow(ba.Window):
text=ba.Lstr(resource=self._r + '.titleText'),
maxwidth=400)
tabs_def = [('my_replays',
ba.Lstr(resource=self._r + '.myReplaysText'))]
tabdefs = [
(TabID.MY_REPLAYS, ba.Lstr(resource=self._r + '.myReplaysText')),
# (TabID.TEST_TAB, ba.Lstr(value='Testing')),
]
scroll_buffer_h = 130 + 2 * x_inset
tab_buffer_h = 750 + 2 * x_inset
self._tab_buttons = tabs.create_tab_buttons(
self._root_widget,
tabs_def,
pos=(tab_buffer_h * 0.5, self._height - 130),
size=(self._width - tab_buffer_h, 50),
on_select_call=self._set_tab)
self._tab_row = TabRow(self._root_widget,
tabdefs,
pos=(tab_buffer_h * 0.5, self._height - 130),
size=(self._width - tab_buffer_h, 50),
on_select_call=self._set_tab)
if ba.app.ui.use_toolbars:
ba.widget(edit=self._tab_buttons[tabs_def[-1][0]],
first_tab = self._tab_row.tabs[tabdefs[0][0]]
last_tab = self._tab_row.tabs[tabdefs[-1][0]]
ba.widget(edit=last_tab.button,
right_widget=_ba.get_special_widget('party_button'))
if uiscale is ba.UIScale.SMALL:
bbtn = _ba.get_special_widget('back_button')
ba.widget(edit=self._tab_buttons[tabs_def[0][0]],
ba.widget(edit=first_tab.button,
up_widget=bbtn,
left_widget=bbtn)
self._scroll_width = self._width - scroll_buffer_h
self._scroll_height = self._height - 180
# not actually using a scroll widget anymore; just an image
# Not actually using a scroll widget anymore; just an image.
scroll_left = (self._width - self._scroll_width) * 0.5
scroll_bottom = self._height - self._scroll_height - 79 - 48
buffer_h = 10
@ -131,21 +141,21 @@ class WatchWindow(ba.Window):
self._restore_state()
def _set_tab(self, tab: str) -> None:
def _set_tab(self, tab_id: TabID) -> None:
# pylint: disable=too-many-locals
from bastd.ui import tabs
if self._current_tab == tab:
if self._current_tab == tab_id:
return
self._current_tab = tab
self._current_tab = tab_id
# We wanna preserve our current tab between runs.
# Preserve our current tab between runs.
cfg = ba.app.config
cfg['Watch Tab'] = tab
cfg['Watch Tab'] = tab_id.value
cfg.commit()
# Update tab colors based on which is selected.
tabs.update_tab_button_colors(self._tab_buttons, tab)
# tabs.update_tab_button_colors(self._tab_buttons, tab)
self._tab_row.update_appearance(tab_id)
if self._tab_container:
self._tab_container.delete()
@ -157,7 +167,7 @@ class WatchWindow(ba.Window):
self._tab_data = {}
uiscale = ba.app.ui.uiscale
if tab == 'my_replays':
if tab_id is TabID.MY_REPLAYS:
c_width = self._scroll_width
c_height = self._scroll_height - 20
sub_scroll_height = c_height - 63
@ -212,7 +222,7 @@ class WatchWindow(ba.Window):
text_scale=tscl,
label=ba.Lstr(resource=self._r + '.watchReplayButtonText'),
autoselect=True)
ba.widget(edit=btn1, up_widget=self._tab_buttons[tab])
ba.widget(edit=btn1, up_widget=self._tab_row.tabs[tab_id].button)
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
ba.widget(edit=btn1,
left_widget=_ba.get_special_widget('back_button'))
@ -255,8 +265,9 @@ class WatchWindow(ba.Window):
ba.widget(edit=scrlw,
autoselect=True,
left_widget=btn1,
up_widget=self._tab_buttons[tab])
ba.widget(edit=self._tab_buttons[tab], down_widget=scrlw)
up_widget=self._tab_row.tabs[tab_id].button)
ba.widget(edit=self._tab_row.tabs[tab_id].button,
down_widget=scrlw)
self._my_replay_selected = None
self._refresh_my_replays()
@ -463,23 +474,28 @@ class WatchWindow(ba.Window):
corner_scale=t_scale,
maxwidth=(self._my_replays_scroll_width / t_scale) * 0.93)
if i == 0:
ba.widget(edit=txt, up_widget=self._tab_buttons['my_replays'])
ba.widget(
edit=txt,
up_widget=self._tab_row.tabs[TabID.MY_REPLAYS].button)
def _save_state(self) -> None:
try:
sel = self._root_widget.get_selected_child()
selected_tab_ids = [
tab_id for tab_id, tab in self._tab_row.tabs.items()
if sel == tab.button
]
if sel == self._back_button:
sel_name = 'Back'
elif sel in list(self._tab_buttons.values()):
sel_name = 'Tab:' + list(self._tab_buttons.keys())[list(
self._tab_buttons.values()).index(sel)]
elif selected_tab_ids:
assert len(selected_tab_ids) == 1
sel_name = f'Tab:{selected_tab_ids[0].value}'
elif sel == self._tab_container:
sel_name = 'TabContainer'
else:
raise ValueError(f'unrecognized selection {sel}')
ba.app.ui.window_states[self.__class__.__name__] = {
'sel_name': sel_name,
'tab': self._current_tab
'sel_name': sel_name
}
except Exception:
ba.print_exception(f'Error saving state for {self}.')
@ -488,21 +504,28 @@ class WatchWindow(ba.Window):
try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__,
{}).get('sel_name')
current_tab = ba.app.config.get('Watch Tab')
if current_tab is None or current_tab not in self._tab_buttons:
current_tab = 'my_replays'
assert isinstance(sel_name, (str, type(None)))
try:
current_tab = TabID(ba.app.config.get('Watch Tab'))
except ValueError:
current_tab = TabID.MY_REPLAYS
self._set_tab(current_tab)
if sel_name == 'Back':
sel = self._back_button
elif sel_name == 'TabContainer':
sel = self._tab_container
elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
sel = self._tab_buttons[sel_name.split(':')[-1]]
try:
sel_tab_id = TabID(sel_name.split(':')[-1])
except ValueError:
sel_tab_id = TabID.MY_REPLAYS
sel = self._tab_row.tabs[sel_tab_id].button
else:
if self._tab_container is not None:
sel = self._tab_container
else:
sel = self._tab_buttons[current_tab]
sel = self._tab_row.tabs[current_tab].button
ba.containerwidget(edit=self._root_widget, selected_child=sel)
except Exception:
ba.print_exception(f'Error restoring state for {self}.')

View File

@ -814,6 +814,7 @@
<w>sval</w>
<w>symbolification</w>
<w>syscalls</w>
<w>tabdefs</w>
<w>talloc</w>
<w>tegra</w>
<w>telefonaktiebolaget</w>

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-10-17 for Ballistica version 1.5.27 build 20223</em></h4>
<h4><em>last updated on 2020-10-19 for Ballistica version 1.5.27 build 20224</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
<hr>

View File

@ -5,23 +5,15 @@
#include <map>
#include "ballistica/app/app.h"
#include "ballistica/app/app_config.h"
#include "ballistica/app/app_globals.h"
#include "ballistica/audio/audio.h"
#include "ballistica/audio/audio_server.h"
#include "ballistica/core/fatal_error.h"
#include "ballistica/core/logging.h"
#include "ballistica/core/thread.h"
#include "ballistica/dynamics/bg/bg_dynamics.h"
#include "ballistica/dynamics/bg/bg_dynamics_server.h"
#include "ballistica/game/account.h"
#include "ballistica/graphics/graphics.h"
#include "ballistica/graphics/graphics_server.h"
#include "ballistica/input/input.h"
#include "ballistica/media/media.h"
#include "ballistica/media/media_server.h"
#include "ballistica/networking/network_write_module.h"
#include "ballistica/networking/networking.h"
#include "ballistica/platform/platform.h"
#include "ballistica/python/python.h"
#include "ballistica/scene/scene.h"
@ -29,7 +21,7 @@
namespace ballistica {
// These are set automatically via script; don't change here.
const int kAppBuildNumber = 20223;
const int kAppBuildNumber = 20224;
const char* kAppVersion = "1.5.27";
// Our standalone globals.