Add actual carts and shit

Upgrade UI to Level 6

Balls
This commit is contained in:
2026-07-06 13:41:15 +02:00
parent f69e832503
commit 8b6c6235c2
17 changed files with 978 additions and 165 deletions
+142 -7
View File
@@ -19,6 +19,7 @@ namespace KernelPanic.UI
private VisualElement detail;
private FontAsset monospaceFont;
private IReadOnlyList<DistroDefinition> units = Array.Empty<DistroDefinition>();
private IReadOnlyList<CardEntry> cards = Array.Empty<CardEntry>();
private int selectedIndex;
public void Bind(VisualElement root, FontAsset artFont)
@@ -28,7 +29,7 @@ namespace KernelPanic.UI
detail = root.Q<VisualElement>("CollectionDetail");
}
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits)
public void RefreshUnits(IReadOnlyList<DistroDefinition> ownedUnits)
{
units = ownedUnits ?? Array.Empty<DistroDefinition>();
if (list == null || detail == null)
@@ -73,6 +74,55 @@ namespace KernelPanic.UI
SelectUnit(selectedIndex);
}
public void RefreshCards(DistroDefinition featuredUnit)
{
cards = BuildCardEntries(featuredUnit);
if (list == null || detail == null)
{
return;
}
rows.Clear();
list.Clear();
if (cards.Count == 0)
{
detail.Clear();
detail.Add(new Label("no cards installed") { name = "CollectionEmptyTitle" });
detail.Add(new Label("featured distro cards appear here") { name = "CollectionEmptyHint" });
return;
}
selectedIndex = Mathf.Clamp(selectedIndex, 0, cards.Count - 1);
for (int i = 0; i < cards.Count; i++)
{
int index = i;
CardEntry entry = cards[i];
VisualElement row = new();
row.AddToClassList("collection-row");
row.RegisterCallback<PointerEnterEvent>(_ => SelectCard(index));
row.RegisterCallback<ClickEvent>(_ => SelectCard(index));
Label name = new(GetCardDisplayName(entry.Card));
name.AddToClassList("collection-row-name");
Label meta = new($"{DistroPresentation.DisplayName(entry.Owner)} / {FormatCardMeta(entry.Card)}");
meta.AddToClassList("collection-row-languages");
row.Add(name);
row.Add(meta);
list.Add(row);
rows.Add(row);
}
SelectCard(selectedIndex);
}
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits)
{
RefreshUnits(ownedUnits);
}
private void SelectUnit(int index)
{
if (units.Count == 0)
@@ -89,10 +139,35 @@ namespace KernelPanic.UI
RenderDetail(units[selectedIndex]);
}
private void SelectCard(int index)
{
if (cards.Count == 0)
{
return;
}
selectedIndex = Mathf.Clamp(index, 0, cards.Count - 1);
for (int i = 0; i < rows.Count; i++)
{
rows[i].EnableInClassList("selected", i == selectedIndex);
}
RenderCardDetail(cards[selectedIndex]);
}
private void RenderDetail(DistroDefinition unit)
{
detail.Clear();
Label name = new(DistroPresentation.DisplayName(unit));
name.AddToClassList("collection-detail-name");
name.style.color = new StyleColor(unit.AccentColor);
detail.Add(name);
Label description = new(string.IsNullOrWhiteSpace(unit.Description) ? "--" : unit.Description);
description.AddToClassList("collection-detail-description");
detail.Add(description);
VisualElement readout = new();
readout.AddToClassList("collection-detail-readout");
@@ -107,11 +182,6 @@ namespace KernelPanic.UI
VisualElement details = new();
details.AddToClassList("collection-detail-values");
Label name = new(DistroPresentation.DisplayName(unit));
name.AddToClassList("collection-detail-name");
name.style.color = new StyleColor(unit.AccentColor);
details.Add(name);
details.Add(BuildDetailLine("lang", DistroPresentation.FormatLanguages(unit)));
details.Add(BuildDetailLine("passive", string.IsNullOrWhiteSpace(unit.PassiveName) ? "--" : unit.PassiveName));
details.Add(BuildDetailLine("uptime", unit.BaseUptime.ToString()));
@@ -120,10 +190,26 @@ namespace KernelPanic.UI
readout.Add(details);
detail.Add(readout);
}
Label description = new(string.IsNullOrWhiteSpace(unit.Description) ? "--" : unit.Description);
private void RenderCardDetail(CardEntry entry)
{
detail.Clear();
CardDefinition card = entry.Card;
Label name = new(GetCardDisplayName(card));
name.AddToClassList("collection-detail-name");
detail.Add(name);
Label description = new(card == null || string.IsNullOrWhiteSpace(card.Description) ? "--" : card.Description);
description.AddToClassList("collection-detail-description");
detail.Add(description);
detail.Add(BuildDetailLine("owner", DistroPresentation.DisplayName(entry.Owner)));
detail.Add(BuildDetailLine("lang", card == null ? "--" : card.Language.ToString()));
detail.Add(BuildDetailLine("rarity", card == null ? "--" : card.Rarity.ToString()));
detail.Add(BuildDetailLine("cost", card == null ? "--" : card.CycleCost.ToString()));
detail.Add(BuildDetailLine("type", card != null && card.IsToken ? "token" : "loadout"));
}
private static VisualElement BuildDetailLine(string key, string value)
@@ -140,5 +226,54 @@ namespace KernelPanic.UI
row.Add(valueLabel);
return row;
}
private static IReadOnlyList<CardEntry> BuildCardEntries(DistroDefinition featuredUnit)
{
List<CardEntry> entries = new();
if (featuredUnit == null)
{
return entries;
}
for (int cardIndex = 0; cardIndex < featuredUnit.ExclusiveCards.Count; cardIndex++)
{
CardDefinition card = featuredUnit.ExclusiveCards[cardIndex];
if (card == null)
{
continue;
}
entries.Add(new CardEntry(featuredUnit, card));
}
return entries;
}
private static string GetCardDisplayName(CardDefinition card)
{
if (card == null)
{
return "--";
}
return string.IsNullOrWhiteSpace(card.DisplayName) ? card.Id : card.DisplayName;
}
private static string FormatCardMeta(CardDefinition card)
{
return card == null ? "--" : $"{card.Language} / {card.CycleCost}c";
}
private readonly struct CardEntry
{
public CardEntry(DistroDefinition owner, CardDefinition card)
{
Owner = owner;
Card = card;
}
public DistroDefinition Owner { get; }
public CardDefinition Card { get; }
}
}
}
+28 -2
View File
@@ -55,12 +55,22 @@ namespace KernelPanic.UI
}
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits)
{
Refresh(ownedUnits, null);
}
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits, DistroDefinition featuredUnit)
{
units = ownedUnits ?? Array.Empty<DistroDefinition>();
Refresh();
Refresh(featuredUnit);
}
public void Refresh()
{
Refresh((DistroDefinition)null);
}
private void Refresh(DistroDefinition featuredUnit)
{
if (units == null || units.Count == 0)
{
@@ -68,7 +78,7 @@ namespace KernelPanic.UI
return;
}
selectedIndex = Mathf.Clamp(selectedIndex, 0, units.Count - 1);
selectedIndex = ResolveSelectedIndex(featuredUnit);
ShowUnit(units[selectedIndex]);
}
@@ -84,6 +94,22 @@ namespace KernelPanic.UI
Refresh();
}
private int ResolveSelectedIndex(DistroDefinition featuredUnit)
{
if (featuredUnit != null)
{
for (int i = 0; i < units.Count; i++)
{
if (units[i] == featuredUnit)
{
return i;
}
}
}
return Mathf.Clamp(selectedIndex, 0, units.Count - 1);
}
private void ShowEmptyState()
{
populatedState.AddToClassList("hidden");
+393 -81
View File
@@ -43,6 +43,8 @@ namespace KernelPanic.UI
[SerializeField] private StarterSelectionController starterSelection = new();
private readonly List<CommandMenuEntry> commandEntries = new();
private readonly List<VisualElement> packageRows = new();
private readonly List<VisualElement> loadoutRows = new();
private UIDocument document;
private VisualElement root;
private VisualElement shellRoot;
@@ -54,6 +56,8 @@ namespace KernelPanic.UI
private VisualElement settingsPanel;
private VisualElement eventBanner;
private VisualElement backgroundLogLayer;
private ScrollView runSetupList;
private VisualElement runSetupDetail;
private Label appIdLabel;
private Label entropyLabel;
private Label pullTokensLabel;
@@ -62,12 +66,12 @@ namespace KernelPanic.UI
private Label bootIntroLogLabel;
private Label motdBodyLabel;
private VisualElement motdBlock;
private Button collectionBackButton;
private Button collectionUnitsButton;
private Button collectionCardsButton;
private Button runSetupBackButton;
private Button gachaBackButton;
private Button settingsBackButton;
private readonly ScreenFrameController runSetupFrame = new();
private readonly ScreenFrameController collectionFrame = new();
private readonly ScreenFrameController gachaFrame = new();
private readonly ScreenFrameController settingsFrame = new();
private SaveService saveService;
private SaveData saveData;
private EntropyWallet wallet;
@@ -77,12 +81,14 @@ namespace KernelPanic.UI
private BackgroundLogRingBuffer backgroundLog;
private IEventBannerSource eventBannerSource;
private int selectedCommandIndex;
private int selectedPackageIndex;
private string runSetupNotice;
private bool collectionShowingCards;
private float bootIntroElapsed;
private int bootIntroCharacterCount;
private bool cursorVisible;
private bool suppressNextClick;
private bool warnedUnresolvedSaveId;
private bool warnedInvalidLoadoutId;
private string bootIntroCopy;
private IVisualElementScheduledItem blinkSchedule;
private IVisualElementScheduledItem bootIntroSchedule;
@@ -106,6 +112,7 @@ namespace KernelPanic.UI
RegisterCommandEntryCallbacks();
featuredUnitPanel.Bind(root, monospaceFont);
collectionScreen.Bind(root, monospaceFont);
BindScreenFrames();
starterSelection.Bind(root, distroDatabase, HandleStarterConfirmed);
LoadMetaState();
playerCollection.Changed += HandleMetaStateChanged;
@@ -121,8 +128,8 @@ namespace KernelPanic.UI
RefreshStaticText();
RefreshCurrencyReadouts();
RefreshEventBanner();
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
collectionScreen.Refresh(playerCollection.OwnedUnits);
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
SelectCommand(0);
ShowMainMenu();
StartAmbientSchedules();
@@ -151,6 +158,8 @@ namespace KernelPanic.UI
eventBanner = root.Q<VisualElement>("EventBanner");
motdBlock = root.Q<VisualElement>("MotdBlock");
backgroundLogLayer = root.Q<VisualElement>("BackgroundLogLayer");
runSetupList = root.Q<ScrollView>("RunSetupList");
runSetupDetail = root.Q<VisualElement>("RunSetupDetail");
backgroundLog = new BackgroundLogRingBuffer(backgroundLogLayer, BootLogCopy.Lines);
appIdLabel = root.Q<Label>("AppIdLabel");
@@ -161,12 +170,16 @@ namespace KernelPanic.UI
bootIntroLogLabel = root.Q<Label>("BootIntroLogLabel");
motdBodyLabel = root.Q<Label>("MotdBodyLabel");
collectionBackButton = root.Q<Button>("CollectionBackButton");
collectionUnitsButton = root.Q<Button>("CollectionUnitsButton");
collectionCardsButton = root.Q<Button>("CollectionCardsButton");
runSetupBackButton = root.Q<Button>("RunSetupBackButton");
gachaBackButton = root.Q<Button>("GachaBackButton");
settingsBackButton = root.Q<Button>("SettingsBackButton");
}
private void BindScreenFrames()
{
runSetupFrame.Bind(runSetupPanel, "$ ./start_run --configure", "[esc] back [arrows] navigate [enter] select [b] boot run", ShowMainMenu);
collectionFrame.Bind(collectionPanel, "$ ls ~/collection", "[esc] back [left/right] tabs [tab] tabs [arrows] navigate [enter] select", ShowMainMenu);
gachaFrame.Bind(gachaPanel, "$ curl gacha.sh | sh", "[esc] back", ShowMainMenu);
settingsFrame.Bind(settingsPanel, "$ dpkg-reconfigure kernel-panic", "[esc] back", ShowMainMenu);
}
private void BindCommandEntries()
@@ -193,24 +206,16 @@ namespace KernelPanic.UI
{
root.RegisterCallback<KeyDownEvent>(HandleKeyDown);
root.RegisterCallback<PointerDownEvent>(HandlePointerDown);
collectionBackButton.clicked += ShowMainMenu;
collectionUnitsButton.clicked += ShowCollectionUnits;
collectionCardsButton.clicked += ShowCollectionCards;
runSetupBackButton.clicked += ShowMainMenu;
gachaBackButton.clicked += ShowMainMenu;
settingsBackButton.clicked += ShowMainMenu;
}
private void UnregisterCallbacks()
{
root.UnregisterCallback<KeyDownEvent>(HandleKeyDown);
root.UnregisterCallback<PointerDownEvent>(HandlePointerDown);
collectionBackButton.clicked -= ShowMainMenu;
collectionUnitsButton.clicked -= ShowCollectionUnits;
collectionCardsButton.clicked -= ShowCollectionCards;
runSetupBackButton.clicked -= ShowMainMenu;
gachaBackButton.clicked -= ShowMainMenu;
settingsBackButton.clicked -= ShowMainMenu;
}
private void LoadMetaState()
@@ -236,27 +241,6 @@ namespace KernelPanic.UI
}
}
for (int i = 0; i < saveData.cardLoadouts.Count; i++)
{
CardLoadoutSaveEntry entry = saveData.cardLoadouts[i];
entry.EnsureLists();
List<string> resolvedIds = new();
for (int cardIndex = 0; cardIndex < entry.equippedCardIds.Count; cardIndex++)
{
string resolvedId = ResolveSavedCardId(entry.equippedCardIds[cardIndex]);
if (!string.IsNullOrWhiteSpace(resolvedId))
{
resolvedIds.Add(resolvedId);
}
}
if (!cardLoadout.TryLoad(entry.distroId, resolvedIds, out bool skippedInvalid) || skippedInvalid)
{
WarnInvalidLoadoutId();
}
}
EnsureLoadoutsForOwnedUnits();
SaveCurrentState();
}
@@ -272,40 +256,11 @@ namespace KernelPanic.UI
return unit;
}
private string ResolveSavedCardId(string id)
{
if (cardDatabase == null)
{
return id;
}
CardDefinition card = cardDatabase.FindById(id);
if (card == null)
{
WarnInvalidLoadoutId();
return null;
}
return card.Id;
}
private void WarnInvalidLoadoutId()
{
if (warnedInvalidLoadoutId)
{
return;
}
warnedInvalidLoadoutId = true;
Debug.LogWarning("Save references an invalid card loadout id; restoring a valid default loadout.");
}
private void HandleMetaStateChanged()
{
EnsureLoadoutsForOwnedUnits();
SaveCurrentState();
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
collectionScreen.Refresh(playerCollection.OwnedUnits);
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
}
private void HandleStarterConfirmed(DistroDefinition picked, IReadOnlyList<DistroDefinition> remaining)
@@ -318,8 +273,8 @@ namespace KernelPanic.UI
saveData.starterChosen = true;
SaveCurrentState();
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
collectionScreen.Refresh(playerCollection.OwnedUnits);
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
}
private void SaveCurrentState()
@@ -347,11 +302,24 @@ namespace KernelPanic.UI
}
}
cardLoadout.WriteTo(saveData.cardLoadouts);
saveService.Save(saveData);
}
private void SaveLastRunLoadout(DistroDefinition unit, IReadOnlyList<string> cardIds)
{
saveData ??= SaveData.CreateDefault();
saveData.EnsureLists();
saveData.lastRunLoadout.distroId = unit == null ? null : unit.Id;
saveData.lastRunLoadout.cardIds.Clear();
for (int i = 0; i < cardIds.Count; i++)
{
saveData.lastRunLoadout.cardIds.Add(cardIds[i]);
}
SaveCurrentState();
}
private void RefreshStaticText()
{
appIdLabel.text = $"kernel-panic v{Application.version} - tty1";
@@ -488,6 +456,13 @@ namespace KernelPanic.UI
return;
}
if (evt.keyCode == KeyCode.Escape && IsSubScreenVisible())
{
ShowMainMenu();
evt.StopPropagation();
return;
}
if (IsRunSetupVisible())
{
if (evt.keyCode == KeyCode.UpArrow)
@@ -504,9 +479,19 @@ namespace KernelPanic.UI
return;
}
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter || evt.keyCode == KeyCode.B)
{
ToggleSelectedPackage();
BootSelectedPackage();
evt.StopPropagation();
return;
}
}
if (IsCollectionVisible())
{
if (evt.keyCode == KeyCode.LeftArrow || evt.keyCode == KeyCode.RightArrow || evt.keyCode == KeyCode.Tab)
{
ToggleCollectionTab();
evt.StopPropagation();
return;
}
@@ -535,7 +520,8 @@ namespace KernelPanic.UI
if (evt.keyCode == KeyCode.Tab)
{
featuredUnitPanel.SelectNext();
playerCollection.SelectNextFeatured();
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
evt.StopPropagation();
return;
}
@@ -554,6 +540,78 @@ namespace KernelPanic.UI
return runSetupPanel != null && !runSetupPanel.ClassListContains(HiddenClassName);
}
private bool IsCollectionVisible()
{
return collectionPanel != null && !collectionPanel.ClassListContains(HiddenClassName);
}
private bool IsSubScreenVisible()
{
return IsRunSetupVisible() || IsCollectionVisible() ||
(gachaPanel != null && !gachaPanel.ClassListContains(HiddenClassName)) ||
(settingsPanel != null && !settingsPanel.ClassListContains(HiddenClassName));
}
private void RefreshRunSetup()
{
packageRows.Clear();
loadoutRows.Clear();
cardLoadout.ClearAll();
runSetupList?.Clear();
runSetupDetail?.Clear();
runSetupNotice = null;
if (runSetupList == null || runSetupDetail == null)
{
return;
}
if (playerCollection.OwnedUnits.Count == 0)
{
runSetupDetail.Add(new Label("no units installed") { name = "RunSetupEmptyTitle" });
runSetupDetail.Add(new Label("install a starter or summon a unit before starting a run") { name = "RunSetupEmptyHint" });
return;
}
selectedPackageIndex = Mathf.Clamp(selectedPackageIndex, 0, playerCollection.OwnedUnits.Count - 1);
for (int i = 0; i < playerCollection.OwnedUnits.Count; i++)
{
int index = i;
DistroDefinition unit = playerCollection.OwnedUnits[i];
VisualElement row = new();
row.AddToClassList("package-row");
row.RegisterCallback<PointerEnterEvent>(_ => SelectPackage(index));
row.RegisterCallback<ClickEvent>(_ => SelectPackage(index));
VisualElement summary = new();
summary.AddToClassList("package-summary");
summary.Add(new Label(">") { name = $"RunSetupMarker{index}" });
summary.ElementAt(0).AddToClassList("package-marker");
Label name = new(DistroPresentation.DisplayName(unit));
name.AddToClassList("package-name");
name.style.color = new StyleColor(unit.AccentColor);
summary.Add(name);
Label meta = new(DistroPresentation.FormatLanguages(unit));
meta.AddToClassList("package-meta");
summary.Add(meta);
row.Add(summary);
row.Add(new Label(string.IsNullOrWhiteSpace(unit.PassiveName) ? "passive: --" : $"passive: {unit.PassiveName}")
{
name = $"RunSetupDescription{index}"
});
row.ElementAt(1).AddToClassList("package-description");
runSetupList.Add(row);
packageRows.Add(row);
}
RefreshPackageSelection();
}
private void SelectPackage(int index)
{
if (packageRows.Count == 0)
@@ -562,9 +620,242 @@ namespace KernelPanic.UI
}
selectedPackageIndex = (index + packageRows.Count) % packageRows.Count;
cardLoadout.ClearLoadout(playerCollection.OwnedUnits[selectedPackageIndex]);
runSetupNotice = null;
RefreshPackageSelection();
}
private void RefreshPackageSelection()
{
for (int i = 0; i < packageRows.Count; i++)
{
packageRows[i].EnableInClassList(SelectedClassName, i == selectedPackageIndex);
}
RenderSelectedPackageDetail();
}
private void RenderSelectedPackageDetail()
{
runSetupDetail?.Clear();
loadoutRows.Clear();
if (runSetupDetail == null || selectedPackageIndex < 0 || selectedPackageIndex >= playerCollection.OwnedUnits.Count)
{
return;
}
DistroDefinition unit = playerCollection.OwnedUnits[selectedPackageIndex];
runSetupDetail.Add(BuildRunSetupReadout(unit));
ScrollView packageScroll = new(ScrollViewMode.Vertical);
packageScroll.AddToClassList("package-scroll");
VisualElement packageList = new();
packageList.AddToClassList("package-list");
packageList.Add(new Label("equipped cards") { name = "RunSetupCardsHeader" });
packageList.ElementAt(0).AddToClassList("package-header");
IReadOnlyList<string> equippedCardIds = cardLoadout.GetEquippedCardIds(unit.Id);
if (!string.IsNullOrWhiteSpace(runSetupNotice))
{
packageList.Add(new Label(runSetupNotice) { name = "RunSetupNotice" });
packageList.ElementAt(packageList.childCount - 1).AddToClassList("package-notice");
}
if (equippedCardIds.Count < CardLoadout.MaxEquippedCards)
{
packageList.Add(new Label($"select {CardLoadout.MaxEquippedCards - equippedCardIds.Count} more card(s) before booting") { name = "RunSetupIncompleteNotice" });
packageList.ElementAt(packageList.childCount - 1).AddToClassList("package-notice");
}
bool addedCards = false;
for (int i = 0; i < unit.ExclusiveCards.Count; i++)
{
CardDefinition card = unit.ExclusiveCards[i];
if (card == null || card.IsToken)
{
continue;
}
bool equipped = ContainsCardId(equippedCardIds, card.Id);
VisualElement row = BuildCardLoadoutRow(unit, card, equipped);
packageList.Add(row);
loadoutRows.Add(row);
addedCards = true;
}
if (!addedCards)
{
packageList.Add(new Label("no compatible cards available") { name = "RunSetupCardsEmpty" });
packageList.ElementAt(packageList.childCount - 1).AddToClassList("package-empty");
}
packageScroll.Add(packageList);
runSetupDetail.Add(packageScroll);
runSetupDetail.Add(BuildBootRow(unit, equippedCardIds.Count > 0));
}
private VisualElement BuildRunSetupReadout(DistroDefinition unit)
{
VisualElement readout = new();
readout.AddToClassList("run-readout");
Label artLabel = new();
DistroArtPresenter.ConfigureArtLabel(artLabel, monospaceFont);
AsciiArtFitter artFitter = new(artLabel, monospaceFont);
VisualElement artPlaceholder = DistroArtPresenter.CreatePlaceholder();
artFitter.SetArt(DistroArtPresenter.Render(artLabel, artPlaceholder, unit));
readout.Add(artPlaceholder);
readout.Add(artLabel);
VisualElement copy = new();
copy.AddToClassList("run-readout-copy");
Label name = new(DistroPresentation.DisplayName(unit));
name.AddToClassList("collection-detail-name");
name.style.color = new StyleColor(unit.AccentColor);
copy.Add(name);
Label languages = new(DistroPresentation.FormatLanguages(unit));
languages.AddToClassList("collection-detail-description");
copy.Add(languages);
Label stats = new($"uptime {unit.BaseUptime} · ram {unit.BaseRam} · cycles {unit.BaseCyclesPerTurn}");
stats.AddToClassList("collection-detail-muted");
copy.Add(stats);
readout.Add(copy);
return readout;
}
private static VisualElement BuildDetailLine(string key, string value)
{
VisualElement row = new();
row.AddToClassList("kv-row");
row.Add(new Label(key) { name = $"RunSetup{key}Key" });
row.ElementAt(0).AddToClassList("kv-key");
row.Add(new Label(value) { name = $"RunSetup{key}Value" });
row.ElementAt(1).AddToClassList("kv-value");
return row;
}
private VisualElement BuildCardLoadoutRow(DistroDefinition unit, CardDefinition card, bool equipped)
{
VisualElement row = new();
row.AddToClassList("package-row");
row.AddToClassList("loadout-card-row");
row.EnableInClassList("equipped", equipped);
row.RegisterCallback<ClickEvent>(_ => ToggleCardInSelectedLoadout(unit, card));
VisualElement summary = new();
summary.AddToClassList("package-summary");
summary.Add(new Label(equipped ? "[x]" : "[ ]") { name = "RunSetupCardMarker" });
summary.ElementAt(0).AddToClassList("package-marker");
string displayName = string.IsNullOrWhiteSpace(card.DisplayName) ? card.Id : card.DisplayName;
summary.Add(new Label(displayName) { name = "RunSetupCardName" });
summary.ElementAt(1).AddToClassList("package-name");
string metaText = $"{card.Language} / {card.CycleCost}c";
summary.Add(new Label(metaText) { name = "RunSetupCardMeta" });
summary.ElementAt(2).AddToClassList("package-meta");
row.Add(summary);
if (!string.IsNullOrWhiteSpace(card.Description))
{
row.Add(new Label(card.Description) { name = "RunSetupCardDescription" });
row.ElementAt(1).AddToClassList("package-description");
}
return row;
}
private void ToggleCardInSelectedLoadout(DistroDefinition unit, CardDefinition card)
{
runSetupNotice = TryToggleLoadoutCard(unit, card);
RenderSelectedPackageDetail();
}
private string TryToggleLoadoutCard(DistroDefinition unit, CardDefinition card)
{
if (unit == null || card == null)
{
return "loadout change failed";
}
IReadOnlyList<string> equippedCardIds = cardLoadout.GetEquippedCardIds(unit.Id);
bool equipped = ContainsCardId(equippedCardIds, card.Id);
CardLoadoutFailureReason reason;
bool changed = equipped
? cardLoadout.TryUnequip(unit.Id, card.Id, out reason)
: cardLoadout.TryEquip(unit.Id, card.Id, out reason);
return changed ? null : FormatLoadoutFailure(reason);
}
private static bool ContainsCardId(IReadOnlyList<string> cardIds, string cardId)
{
for (int i = 0; i < cardIds.Count; i++)
{
if (string.Equals(cardIds[i], cardId, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
private static string FormatLoadoutFailure(CardLoadoutFailureReason reason)
{
return reason switch
{
CardLoadoutFailureReason.Full => $"loadout already has {CardLoadout.MaxEquippedCards} cards",
CardLoadoutFailureReason.Duplicate => "card is already equipped",
CardLoadoutFailureReason.Token => "token cards cannot be equipped",
CardLoadoutFailureReason.NotEquipped => "card is not equipped",
CardLoadoutFailureReason.NotOwned => "card is not owned by this distro",
_ => "loadout change failed"
};
}
private VisualElement BuildBootRow(DistroDefinition unit, bool enabled)
{
VisualElement row = new();
row.AddToClassList("command-row");
row.AddToClassList("boot-command-row");
row.EnableInClassList("disabled-row", !enabled);
row.RegisterCallback<ClickEvent>(_ => BootSelectedPackage());
row.Add(new Label(">") { name = "BootCommandCursor" });
row.ElementAt(0).AddToClassList("command-cursor");
row.Add(new Label($"./boot --distro={unit.Id}") { name = "BootCommandText" });
row.ElementAt(1).AddToClassList("command-text");
row.Add(new Label("start run") { name = "BootCommandDescription" });
row.ElementAt(2).AddToClassList("command-description");
return row;
}
private void BootSelectedPackage()
{
if (selectedPackageIndex < 0 || selectedPackageIndex >= playerCollection.OwnedUnits.Count)
{
return;
}
DistroDefinition unit = playerCollection.OwnedUnits[selectedPackageIndex];
IReadOnlyList<string> equippedCardIds = cardLoadout.GetEquippedCardIds(unit.Id);
if (equippedCardIds.Count < 1)
{
runSetupNotice = "boot: no packages staged";
RenderSelectedPackageDetail();
return;
}
SaveLastRunLoadout(unit, equippedCardIds);
SceneLoader.LoadGame();
}
private void HandlePointerDown(PointerDownEvent evt)
{
root.Focus();
@@ -613,26 +904,47 @@ namespace KernelPanic.UI
private void ShowMainMenu()
{
cardLoadout.ClearAll();
runSetupNotice = null;
ShowPanel(mainMenuPanel);
root.Focus();
}
private void ShowCollection()
{
collectionScreen.Refresh(playerCollection.OwnedUnits);
ShowCollectionUnits();
ShowPanel(collectionPanel);
}
private void ShowCollectionUnits()
{
collectionShowingCards = false;
RefreshCollection();
collectionUnitsButton.EnableInClassList(SelectedClassName, true);
collectionCardsButton.EnableInClassList(SelectedClassName, false);
collectionFrame.SetTitle("$ ls ~/collection");
collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
}
private void ShowCollectionCards()
{
collectionShowingCards = true;
RefreshCollection();
collectionUnitsButton.EnableInClassList(SelectedClassName, false);
collectionCardsButton.EnableInClassList(SelectedClassName, true);
DistroDefinition featuredUnit = playerCollection.FeaturedUnit;
string distroId = featuredUnit == null ? "--" : featuredUnit.Id;
collectionFrame.SetTitle($"$ dpkg -l --distro={distroId}");
collectionScreen.RefreshCards(playerCollection.FeaturedUnit);
}
private void ToggleCollectionTab()
{
if (collectionShowingCards)
{
ShowCollectionUnits();
return;
}
ShowCollectionCards();
}
private void ShowGacha()