fix shit and stuff

This commit is contained in:
2026-07-03 22:13:28 +02:00
parent 2fc8a05617
commit 36ddc8fa1c
5 changed files with 266 additions and 56 deletions
+2 -2
View File
@@ -304,5 +304,5 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
monospaceFont: {fileID: 0}
featuredUnitPanel:
featuredUnits: []
motdBody: unstable userspace detected; keep a rollback shell open.
featuredUnitPanel: {}
+16 -9
View File
@@ -15,8 +15,6 @@ namespace KernelPanic.UI
private const string EmptyAccent = "5cff91";
private const string EmptyAscii = " _\n | |\n ___| |__\n / __| '_ \\\n \\__ \\ | | |\n |___/_| |_|";
[SerializeField] private List<DistroDefinition> featuredUnits = new();
private VisualElement panel;
private Label titleLabel;
private Label asciiLabel;
@@ -28,6 +26,7 @@ namespace KernelPanic.UI
private Label emptyHintLabel;
private VisualElement populatedState;
private VisualElement emptyState;
private IReadOnlyList<DistroDefinition> units = Array.Empty<DistroDefinition>();
private int selectedIndex;
public void Bind(VisualElement root)
@@ -45,26 +44,33 @@ namespace KernelPanic.UI
emptyState = root.Q<VisualElement>("FeaturedUnitEmptyState");
}
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits)
{
units = ownedUnits ?? Array.Empty<DistroDefinition>();
Refresh();
}
public void Refresh()
{
if (featuredUnits == null || featuredUnits.Count == 0)
if (units == null || units.Count == 0)
{
ShowEmptyState();
return;
}
selectedIndex = Mathf.Clamp(selectedIndex, 0, featuredUnits.Count - 1);
ShowUnit(featuredUnits[selectedIndex]);
selectedIndex = Mathf.Clamp(selectedIndex, 0, units.Count - 1);
ShowUnit(units[selectedIndex]);
}
public void SelectNext()
{
if (featuredUnits == null || featuredUnits.Count < 2)
int unitCount = units?.Count ?? 0;
if (unitCount < 2)
{
return;
}
selectedIndex = (selectedIndex + 1) % featuredUnits.Count;
selectedIndex = (selectedIndex + 1) % unitCount;
Refresh();
}
@@ -73,7 +79,7 @@ namespace KernelPanic.UI
populatedState.AddToClassList("hidden");
emptyState.RemoveFromClassList("hidden");
titleLabel.text = "neofetch";
titleLabel.text = "[ neofetch ]";
emptyTitleLabel.text = "no units installed";
emptyHintLabel.text = "run: curl gacha.sh | sh";
SetAccent(EmptyAccent);
@@ -85,7 +91,7 @@ namespace KernelPanic.UI
emptyState.AddToClassList("hidden");
string displayName = string.IsNullOrWhiteSpace(unit.DisplayName) ? unit.name : unit.DisplayName;
titleLabel.text = displayName;
titleLabel.text = "[ neofetch ]";
asciiLabel.text = EmptyAscii; // TODO: Replace with unit-specific ASCII copy when unit presentation data exists.
unitNameLabel.text = displayName;
languagesLabel.text = $"{unit.PrimaryLanguage} / {unit.SecondaryLanguage}";
@@ -103,6 +109,7 @@ namespace KernelPanic.UI
panel.style.borderBottomColor = new StyleColor(parsedColor);
panel.style.borderLeftColor = new StyleColor(parsedColor);
titleLabel.style.color = new StyleColor(parsedColor);
unitNameLabel.style.color = new StyleColor(parsedColor);
}
}
}
+22 -23
View File
@@ -25,6 +25,7 @@ namespace KernelPanic.UI
private static bool bootIntroPlayed;
[SerializeField] private FontAsset monospaceFont; // TODO: Assign a real monospace FontAsset when typography assets exist.
[SerializeField] private string motdBody = "unstable userspace detected; keep a rollback shell open.";
[SerializeField] private FeaturedUnitPanel featuredUnitPanel = new();
private readonly List<CommandMenuEntry> commandEntries = new();
@@ -44,21 +45,23 @@ namespace KernelPanic.UI
private Label titleCursorLabel;
private Label promptCursorLabel;
private Label bootIntroLogLabel;
private Label backgroundLogLabel;
private Label motdBodyLabel;
private VisualElement motdBlock;
private Button collectionBackButton;
private Button gachaBackButton;
private Button settingsBackButton;
private EntropyWallet wallet;
private GachaService gachaService;
private PlayerCollection playerCollection;
private BackgroundLogRingBuffer backgroundLog;
private IEventBannerSource eventBannerSource;
private int selectedCommandIndex;
private float backgroundScrollOffset;
private float bootIntroElapsed;
private int bootIntroCharacterCount;
private bool cursorVisible;
private bool suppressNextClick;
private string bootIntroCopy;
private IVisualElementScheduledItem blinkSchedule;
private IVisualElementScheduledItem backgroundSchedule;
private IVisualElementScheduledItem bootIntroSchedule;
public void Initialize(EntropyWallet initializedWallet)
@@ -71,6 +74,7 @@ namespace KernelPanic.UI
{
document = GetComponent<UIDocument>();
gachaService = new GachaService();
playerCollection = new PlayerCollection(); // TODO: Replace with persistent player-collection service composition.
Initialize(new EntropyWallet()); // TODO: Replace with persistent wallet service composition.
BindElements();
BindCommandEntries();
@@ -87,7 +91,7 @@ namespace KernelPanic.UI
RefreshStaticText();
RefreshCurrencyReadouts();
RefreshEventBanner();
featuredUnitPanel.Refresh();
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
SelectCommand(0);
ShowMainMenu();
StartAmbientSchedules();
@@ -98,7 +102,7 @@ namespace KernelPanic.UI
{
UnregisterCallbacks();
blinkSchedule?.Pause();
backgroundSchedule?.Pause();
backgroundLog?.Stop();
bootIntroSchedule?.Pause();
}
@@ -112,7 +116,9 @@ namespace KernelPanic.UI
gachaPanel = root.Q<VisualElement>("GachaPanel");
settingsPanel = root.Q<VisualElement>("SettingsPanel");
eventBanner = root.Q<VisualElement>("EventBanner");
motdBlock = root.Q<VisualElement>("MotdBlock");
backgroundLogLayer = root.Q<VisualElement>("BackgroundLogLayer");
backgroundLog = new BackgroundLogRingBuffer(backgroundLogLayer, BootLogCopy.Lines);
appIdLabel = root.Q<Label>("AppIdLabel");
entropyLabel = root.Q<Label>("EntropyLabel");
@@ -120,7 +126,7 @@ namespace KernelPanic.UI
titleCursorLabel = root.Q<Label>("TitleCursorLabel");
promptCursorLabel = root.Q<Label>("PromptCursorLabel");
bootIntroLogLabel = root.Q<Label>("BootIntroLogLabel");
backgroundLogLabel = root.Q<Label>("BackgroundLogLabel");
motdBodyLabel = root.Q<Label>("MotdBodyLabel");
collectionBackButton = root.Q<Button>("CollectionBackButton");
gachaBackButton = root.Q<Button>("GachaBackButton");
@@ -168,7 +174,8 @@ namespace KernelPanic.UI
private void RefreshStaticText()
{
appIdLabel.text = $"kernel-panic v{Application.version} - tty1";
backgroundLogLabel.text = BuildBackgroundLog();
motdBodyLabel.text = motdBody ?? string.Empty;
motdBlock.EnableInClassList(HiddenClassName, string.IsNullOrWhiteSpace(motdBody));
bootIntroCopy = string.Join("\n", BootLogCopy.Lines);
}
@@ -210,26 +217,23 @@ namespace KernelPanic.UI
private void StartAmbientSchedules()
{
blinkSchedule?.Pause();
backgroundLog?.Stop();
cursorVisible = true;
titleCursorLabel.EnableInClassList(CursorOnClassName, true);
promptCursorLabel.EnableInClassList(CursorOnClassName, true);
if (UIPreferences.ReducedMotion)
{
titleCursorLabel.AddToClassList(CursorOnClassName);
promptCursorLabel.AddToClassList(CursorOnClassName);
return;
}
bool cursorVisible = true;
blinkSchedule = root.schedule.Execute(() =>
{
cursorVisible = !cursorVisible;
titleCursorLabel.EnableInClassList(CursorOnClassName, cursorVisible);
promptCursorLabel.EnableInClassList(CursorOnClassName, cursorVisible);
}).Every(480);
backgroundSchedule = root.schedule.Execute(() =>
{
backgroundScrollOffset = (backgroundScrollOffset + 0.35f) % 80f;
backgroundLogLayer.style.translate = new Translate(0, -backgroundScrollOffset, 0);
}).Every(33);
}).Every(500);
}
private void PlayBootIntroIfNeeded()
@@ -272,6 +276,7 @@ namespace KernelPanic.UI
bootIntroPanel.AddToClassList(HiddenClassName);
shellRoot.RemoveFromClassList("boot-hidden");
shellRoot.AddToClassList("boot-visible");
backgroundLog?.Start(UIPreferences.ReducedMotion);
root.Focus();
}
@@ -419,12 +424,6 @@ namespace KernelPanic.UI
settingsPanel.EnableInClassList(HiddenClassName, settingsPanel != activePanel);
}
private static string BuildBackgroundLog()
{
string block = string.Join("\n", BootLogCopy.Lines);
return $"{block}\n{block}\n{block}\n{block}";
}
private sealed class CommandMenuEntry
{
private readonly Label cursor;
+183 -16
View File
@@ -1,5 +1,6 @@
:root {
--kp-bg: #050807;
--kp-bg-solid: #050807;
--kp-panel: #09100d;
--kp-primary: #5cff91;
--kp-primary-dim: #2f7a4d;
@@ -16,19 +17,27 @@
font-size: 16px;
}
Label,
Button {
color: var(--kp-primary);
}
.background-log-layer {
position: absolute;
left: 24px;
right: 24px;
top: 0;
bottom: -120px;
opacity: 0.07;
top: 52px;
bottom: 50px;
opacity: 0.04;
overflow: hidden;
}
.background-log {
.background-log-line {
height: 18px;
color: var(--kp-primary);
white-space: normal;
font-size: 14px;
line-height: 18px;
white-space: nowrap;
}
.boot-intro {
@@ -52,6 +61,7 @@
flex-grow: 1;
padding: 18px 24px 16px 24px;
opacity: 1;
justify-content: space-between;
transition-property: opacity;
transition-duration: 220ms;
}
@@ -83,6 +93,7 @@
border-top-width: 0;
border-bottom-width: 1px;
border-bottom-color: var(--kp-border);
background-color: var(--kp-bg-solid);
}
.footer-bar {
@@ -116,19 +127,21 @@
.main-panel {
flex-grow: 1;
justify-content: center;
}
.content-row {
flex-grow: 1;
flex-direction: row;
padding-top: 52px;
padding-bottom: 42px;
align-items: center;
padding-top: 32px;
padding-bottom: 28px;
}
.command-column {
flex-grow: 1;
min-width: 470px;
padding-right: 40px;
background-color: var(--kp-bg-solid);
}
.terminal-title {
@@ -148,7 +161,7 @@
.tagline {
margin-top: 4px;
margin-bottom: 34px;
margin-bottom: 28px;
font-size: 16px;
}
@@ -157,13 +170,13 @@
}
.command-row {
min-height: 44px;
min-height: 38px;
flex-direction: row;
align-items: center;
padding-left: 10px;
padding-right: 10px;
margin-top: 4px;
margin-bottom: 4px;
margin-top: 3px;
margin-bottom: 3px;
border-left-width: 2px;
border-left-color: transparent;
}
@@ -197,7 +210,8 @@
.featured-panel {
width: 430px;
min-height: 330px;
padding: 18px;
max-height: 340px;
padding: 0;
border-left-width: 1px;
border-right-width: 1px;
border-top-width: 1px;
@@ -206,10 +220,25 @@
border-right-color: var(--kp-primary);
border-top-color: var(--kp-primary);
border-bottom-color: var(--kp-primary);
background-color: var(--kp-panel);
background-color: var(--kp-bg-solid);
}
.featured-title-bar {
height: 32px;
flex-direction: row;
align-items: center;
padding-left: 12px;
padding-right: 12px;
border-bottom-width: 1px;
border-bottom-color: var(--kp-border);
background-color: var(--kp-bg-solid);
}
.featured-title {
color: var(--kp-muted);
font-size: 12px;
}
.featured-title,
.screen-title {
color: var(--kp-primary);
font-size: 24px;
@@ -219,8 +248,18 @@
.featured-empty {
flex-grow: 1;
justify-content: center;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 18px;
}
.featured-empty-copy {
flex-grow: 1;
align-items: center;
justify-content: center;
min-height: 150px;
padding-left: 20px;
}
.empty-title {
@@ -231,31 +270,159 @@
.featured-populated {
flex-direction: row;
padding: 18px;
overflow: hidden;
}
.ascii-art {
width: 150px;
min-width: 150px;
color: var(--kp-primary);
white-space: normal;
}
.ascii-placeholder {
width: 140px;
height: 150px;
position: relative;
}
.dash {
position: absolute;
background-color: var(--kp-primary);
opacity: 0.25;
}
.dash-top,
.dash-bottom {
width: 10px;
height: 1px;
}
.dash-left,
.dash-right {
width: 1px;
height: 10px;
}
.dash-top {
top: 0;
}
.dash-bottom {
bottom: 0;
}
.dash-left {
left: 0;
}
.dash-right {
right: 0;
}
.dash-01 {
left: 0;
}
.dash-02 {
left: 19px;
}
.dash-03 {
left: 38px;
}
.dash-04 {
left: 57px;
}
.dash-05 {
left: 76px;
}
.dash-06 {
left: 95px;
}
.dash-07 {
left: 114px;
}
.dash-08 {
left: 130px;
}
.dash-v01 {
top: 0;
}
.dash-v02 {
top: 27px;
}
.dash-v03 {
top: 54px;
}
.dash-v04 {
top: 81px;
}
.dash-v05 {
top: 108px;
}
.dash-v06 {
top: 140px;
}
.unit-readout {
flex-grow: 1;
min-width: 0;
max-width: 226px;
overflow: hidden;
}
.kv-row {
flex-direction: row;
margin-bottom: 8px;
overflow: hidden;
}
.kv-key {
width: 88px;
min-width: 88px;
color: var(--kp-muted);
}
.kv-value {
flex-grow: 1;
min-width: 0;
max-width: 138px;
color: var(--kp-primary);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.motd-block {
max-width: 55%;
align-self: flex-start;
margin-bottom: 14px;
}
.motd-header {
color: var(--kp-muted);
font-size: 13px;
margin-bottom: 4px;
}
.motd-body {
color: var(--kp-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.event-banner {
+43 -6
View File
@@ -4,9 +4,7 @@
<ui:Template name="GachaScreen" src="GachaScreen.uxml" />
<ui:Template name="SettingsScreen" src="SettingsScreen.uxml" />
<ui:VisualElement name="Root" class="terminal-root" focusable="true">
<ui:VisualElement name="BackgroundLogLayer" class="background-log-layer" pickingMode="Ignore">
<ui:Label name="BackgroundLogLabel" class="background-log" />
</ui:VisualElement>
<ui:VisualElement name="BackgroundLogLayer" class="background-log-layer" pickingMode="Ignore" />
<ui:VisualElement name="BootIntroPanel" class="boot-intro">
<ui:Label name="BootIntroLogLabel" class="boot-intro-log" />
@@ -61,10 +59,44 @@
</ui:VisualElement>
<ui:VisualElement name="FeaturedUnitPanel" class="featured-panel">
<ui:Label name="FeaturedUnitTitle" text="neofetch" class="featured-title" />
<ui:VisualElement class="featured-title-bar">
<ui:Label name="FeaturedUnitTitle" text="[ neofetch ]" class="featured-title" />
</ui:VisualElement>
<ui:VisualElement name="FeaturedUnitEmptyState" class="featured-empty">
<ui:Label name="FeaturedUnitEmptyTitle" text="no units installed" class="empty-title" />
<ui:Label name="FeaturedUnitEmptyHint" text="run: curl gacha.sh | sh" class="muted-text" />
<ui:VisualElement name="FeaturedUnitEmptyArt" class="ascii-placeholder">
<ui:VisualElement class="dash dash-top dash-01" />
<ui:VisualElement class="dash dash-top dash-02" />
<ui:VisualElement class="dash dash-top dash-03" />
<ui:VisualElement class="dash dash-top dash-04" />
<ui:VisualElement class="dash dash-top dash-05" />
<ui:VisualElement class="dash dash-top dash-06" />
<ui:VisualElement class="dash dash-top dash-07" />
<ui:VisualElement class="dash dash-top dash-08" />
<ui:VisualElement class="dash dash-bottom dash-01" />
<ui:VisualElement class="dash dash-bottom dash-02" />
<ui:VisualElement class="dash dash-bottom dash-03" />
<ui:VisualElement class="dash dash-bottom dash-04" />
<ui:VisualElement class="dash dash-bottom dash-05" />
<ui:VisualElement class="dash dash-bottom dash-06" />
<ui:VisualElement class="dash dash-bottom dash-07" />
<ui:VisualElement class="dash dash-bottom dash-08" />
<ui:VisualElement class="dash dash-left dash-v01" />
<ui:VisualElement class="dash dash-left dash-v02" />
<ui:VisualElement class="dash dash-left dash-v03" />
<ui:VisualElement class="dash dash-left dash-v04" />
<ui:VisualElement class="dash dash-left dash-v05" />
<ui:VisualElement class="dash dash-left dash-v06" />
<ui:VisualElement class="dash dash-right dash-v01" />
<ui:VisualElement class="dash dash-right dash-v02" />
<ui:VisualElement class="dash dash-right dash-v03" />
<ui:VisualElement class="dash dash-right dash-v04" />
<ui:VisualElement class="dash dash-right dash-v05" />
<ui:VisualElement class="dash dash-right dash-v06" />
</ui:VisualElement>
<ui:VisualElement class="featured-empty-copy">
<ui:Label name="FeaturedUnitEmptyTitle" text="no units installed" class="empty-title" />
<ui:Label name="FeaturedUnitEmptyHint" text="run: curl gacha.sh | sh" class="muted-text" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="FeaturedUnitPopulatedState" class="featured-populated hidden">
<ui:Label name="FeaturedUnitAscii" class="ascii-art" />
@@ -99,6 +131,11 @@
<ui:Label name="EventBannerLabel" class="event-banner-text" />
</ui:VisualElement>
<ui:VisualElement name="MotdBlock" class="motd-block">
<ui:Label text="cat /etc/motd" class="motd-header" />
<ui:Label name="MotdBodyLabel" class="motd-body" />
</ui:VisualElement>
<ui:VisualElement class="footer-bar">
<ui:VisualElement class="prompt-row">
<ui:Label text="player@mainframe:~$ " class="prompt-text" />