fix(studio): polish character skill rows
This commit is contained in:
parent
5f92493742
commit
ffaf593c76
3 changed files with 47 additions and 19 deletions
|
|
@ -138,6 +138,8 @@ public static class CharacterStatController
|
||||||
/// <summary>Row highlight color — semi-translucent gold, matches retail
|
/// <summary>Row highlight color — semi-translucent gold, matches retail
|
||||||
/// UIStateId.Highlight (0x06) sprite 0x06001397 visual intent.</summary>
|
/// UIStateId.Highlight (0x06) sprite 0x06001397 visual intent.</summary>
|
||||||
private static readonly Vector4 HighlightBg = new(1f, 0.75f, 0.2f, 0.25f);
|
private static readonly Vector4 HighlightBg = new(1f, 0.75f, 0.2f, 0.25f);
|
||||||
|
private static readonly Vector4 SkillRowBackground = new(0f, 0f, 0f, 1f);
|
||||||
|
private static readonly Vector4 BuffedSkillGreen = new(0.55f, 1f, 0.55f, 1f);
|
||||||
|
|
||||||
// ── Row layout constants ─────────────────────────────────────────────────
|
// ── Row layout constants ─────────────────────────────────────────────────
|
||||||
// RowHeight 22px + IconSize 16px: retail spec (2026-06-26) says icons ~icon-height
|
// RowHeight 22px + IconSize 16px: retail spec (2026-06-26) says icons ~icon-height
|
||||||
|
|
@ -155,8 +157,8 @@ public static class CharacterStatController
|
||||||
|
|
||||||
private const uint SkillHeaderSpecializedSprite = 0x06000F90u;
|
private const uint SkillHeaderSpecializedSprite = 0x06000F90u;
|
||||||
private const uint SkillHeaderTrainedSprite = 0x06000F86u;
|
private const uint SkillHeaderTrainedSprite = 0x06000F86u;
|
||||||
private const uint SkillHeaderUntrainedSprite = 0x06000F89u;
|
private const uint SkillHeaderUntrainedSprite = 0x06000F98u;
|
||||||
private const uint SkillRowBgSprite = 0x06000F98u;
|
private const uint SkillHeaderUnusableSprite = 0x06000F89u;
|
||||||
private const uint RowHighlightSprite = 0x06001397u;
|
private const uint RowHighlightSprite = 0x06001397u;
|
||||||
|
|
||||||
private enum CharacterStatTab
|
private enum CharacterStatTab
|
||||||
|
|
@ -583,9 +585,9 @@ public static class CharacterStatController
|
||||||
OrderedSkills(data(), CharacterSkillAdvancementClass.Specialized, usableUntrained: null));
|
OrderedSkills(data(), CharacterSkillAdvancementClass.Specialized, usableUntrained: null));
|
||||||
AddBucket("Trained Skills", SkillHeaderTrainedSprite,
|
AddBucket("Trained Skills", SkillHeaderTrainedSprite,
|
||||||
OrderedSkills(data(), CharacterSkillAdvancementClass.Trained, usableUntrained: null));
|
OrderedSkills(data(), CharacterSkillAdvancementClass.Trained, usableUntrained: null));
|
||||||
AddBucket("Useable Untrained Skills", SkillHeaderUntrainedSprite,
|
AddBucket("Untrained Skills", SkillHeaderUntrainedSprite,
|
||||||
OrderedSkills(data(), CharacterSkillAdvancementClass.Untrained, usableUntrained: true));
|
OrderedSkills(data(), CharacterSkillAdvancementClass.Untrained, usableUntrained: true));
|
||||||
AddBucket("Unuseable Untrained Skills", SkillHeaderUntrainedSprite,
|
AddBucket("Unusable Skills", SkillHeaderUnusableSprite,
|
||||||
OrderedSkills(data(), CharacterSkillAdvancementClass.Untrained, usableUntrained: false));
|
OrderedSkills(data(), CharacterSkillAdvancementClass.Untrained, usableUntrained: false));
|
||||||
|
|
||||||
skillRows = bindings;
|
skillRows = bindings;
|
||||||
|
|
@ -606,7 +608,8 @@ public static class CharacterStatController
|
||||||
nameText: skill.Name,
|
nameText: skill.Name,
|
||||||
valueProvider: () => skill.CurrentLevel.ToString(),
|
valueProvider: () => skill.CurrentLevel.ToString(),
|
||||||
valueColorProvider: () => SkillValueColor(skill),
|
valueColorProvider: () => SkillValueColor(skill),
|
||||||
backgroundSprite: SkillRowBgSprite,
|
nameColor: Vector4.One,
|
||||||
|
backgroundColor: SkillRowBackground,
|
||||||
useSelectionBars: false);
|
useSelectionBars: false);
|
||||||
row.OnClick = () => HandleSkillRowClick(rowIndex, sel, bindings, spriteResolve, data, allRaise1, allRaise10);
|
row.OnClick = () => HandleSkillRowClick(rowIndex, sel, bindings, spriteResolve, data, allRaise1, allRaise10);
|
||||||
bindings.Add(new SkillRowBinding(row, skill));
|
bindings.Add(new SkillRowBinding(row, skill));
|
||||||
|
|
@ -656,7 +659,7 @@ public static class CharacterStatController
|
||||||
Anchors = AnchorEdges.Left | AnchorEdges.Top,
|
Anchors = AnchorEdges.Left | AnchorEdges.Top,
|
||||||
};
|
};
|
||||||
string captured = title;
|
string captured = title;
|
||||||
label.LinesProvider = () => new[] { new UiText.Line(captured, Gold) };
|
label.LinesProvider = () => new[] { new UiText.Line(captured, Vector4.One) };
|
||||||
header.AddChild(label);
|
header.AddChild(label);
|
||||||
list.AddChild(header);
|
list.AddChild(header);
|
||||||
return header;
|
return header;
|
||||||
|
|
@ -700,9 +703,9 @@ public static class CharacterStatController
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector4 SkillValueColor(CharacterSkill skill)
|
private static Vector4 SkillValueColor(CharacterSkill skill)
|
||||||
=> skill.CurrentLevel > skill.BaseLevel ? new Vector4(0.55f, 1f, 0.55f, 1f)
|
=> skill.CurrentLevel > skill.BaseLevel ? BuffedSkillGreen
|
||||||
: skill.CurrentLevel < skill.BaseLevel ? new Vector4(1f, 0.45f, 0.45f, 1f)
|
: skill.CurrentLevel < skill.BaseLevel ? new Vector4(1f, 0.45f, 0.45f, 1f)
|
||||||
: Body;
|
: Vector4.One;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles a row click: toggle (same row → deselect), else select new row.
|
/// Handles a row click: toggle (same row → deselect), else select new row.
|
||||||
|
|
@ -781,7 +784,7 @@ public static class CharacterStatController
|
||||||
{
|
{
|
||||||
if (spriteResolve is not null)
|
if (spriteResolve is not null)
|
||||||
{
|
{
|
||||||
row.BackgroundColor = Vector4.Zero;
|
row.BackgroundColor = SkillRowBackground;
|
||||||
row.BackgroundSprite = RowHighlightSprite;
|
row.BackgroundSprite = RowHighlightSprite;
|
||||||
row.SpriteResolve = id => { var (h, w, ht) = spriteResolve(id); return (h, w, ht); };
|
row.SpriteResolve = id => { var (h, w, ht) = spriteResolve(id); return (h, w, ht); };
|
||||||
row.UseSelectionBars = true;
|
row.UseSelectionBars = true;
|
||||||
|
|
@ -796,11 +799,9 @@ public static class CharacterStatController
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
row.BackgroundColor = Vector4.Zero;
|
row.BackgroundColor = SkillRowBackground;
|
||||||
row.BackgroundSprite = spriteResolve is not null ? SkillRowBgSprite : 0u;
|
row.BackgroundSprite = 0u;
|
||||||
row.SpriteResolve = spriteResolve is not null
|
row.SpriteResolve = null;
|
||||||
? id => { var (h, w, ht) = spriteResolve(id); return (h, w, ht); }
|
|
||||||
: null;
|
|
||||||
row.UseSelectionBars = false;
|
row.UseSelectionBars = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -931,6 +932,8 @@ public static class CharacterStatController
|
||||||
string nameText,
|
string nameText,
|
||||||
Func<string> valueProvider,
|
Func<string> valueProvider,
|
||||||
Func<Vector4>? valueColorProvider = null,
|
Func<Vector4>? valueColorProvider = null,
|
||||||
|
Vector4? nameColor = null,
|
||||||
|
Vector4? backgroundColor = null,
|
||||||
uint backgroundSprite = 0u,
|
uint backgroundSprite = 0u,
|
||||||
bool useSelectionBars = true)
|
bool useSelectionBars = true)
|
||||||
{
|
{
|
||||||
|
|
@ -940,7 +943,7 @@ public static class CharacterStatController
|
||||||
Top = top,
|
Top = top,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
BackgroundColor = Vector4.Zero, // transparent until selected
|
BackgroundColor = backgroundColor ?? Vector4.Zero,
|
||||||
BackgroundSprite = spriteResolve is not null ? backgroundSprite : 0u,
|
BackgroundSprite = spriteResolve is not null ? backgroundSprite : 0u,
|
||||||
SpriteResolve = spriteResolve is not null && backgroundSprite != 0u
|
SpriteResolve = spriteResolve is not null && backgroundSprite != 0u
|
||||||
? id => { var (h, w, ht) = spriteResolve(id); return (h, w, ht); }
|
? id => { var (h, w, ht) = spriteResolve(id); return (h, w, ht); }
|
||||||
|
|
@ -973,6 +976,7 @@ public static class CharacterStatController
|
||||||
float nameY = 0f;
|
float nameY = 0f;
|
||||||
|
|
||||||
string capturedName = nameText;
|
string capturedName = nameText;
|
||||||
|
Vector4 capturedNameColor = nameColor ?? Body;
|
||||||
var nameEl = new UiText
|
var nameEl = new UiText
|
||||||
{
|
{
|
||||||
Left = nameX,
|
Left = nameX,
|
||||||
|
|
@ -986,7 +990,7 @@ public static class CharacterStatController
|
||||||
Padding = 1f,
|
Padding = 1f,
|
||||||
Anchors = AnchorEdges.Left | AnchorEdges.Top,
|
Anchors = AnchorEdges.Left | AnchorEdges.Top,
|
||||||
};
|
};
|
||||||
nameEl.LinesProvider = () => new[] { new UiText.Line(capturedName, Body) };
|
nameEl.LinesProvider = () => new[] { new UiText.Line(capturedName, capturedNameColor) };
|
||||||
|
|
||||||
float valueW = width - nameX - nameW - RowPadX;
|
float valueW = width - nameX - nameW - RowPadX;
|
||||||
float valueX = nameX + nameW;
|
float valueX = nameX + nameW;
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,9 @@ public class UiClickablePanel : UiPanel
|
||||||
{
|
{
|
||||||
if (UseSelectionBars && BackgroundSprite != 0 && SpriteResolve is { } sr)
|
if (UseSelectionBars && BackgroundSprite != 0 && SpriteResolve is { } sr)
|
||||||
{
|
{
|
||||||
|
if (BackgroundColor.W > 0f)
|
||||||
|
ctx.DrawRect(0, 0, Width, Height, BackgroundColor);
|
||||||
|
|
||||||
// Draw the selection highlight as a thin bar at the TOP and BOTTOM of the row.
|
// Draw the selection highlight as a thin bar at the TOP and BOTTOM of the row.
|
||||||
// The sprite (0x06001397) is 300×32 px — we draw it as horizontal strips at
|
// The sprite (0x06001397) is 300×32 px — we draw it as horizontal strips at
|
||||||
// native height (SelectionBarHeight), stretched to full panel width (UV tile
|
// native height (SelectionBarHeight), stretched to full panel width (UV tile
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using AcDream.App.Studio;
|
using AcDream.App.Studio;
|
||||||
using AcDream.App.UI;
|
using AcDream.App.UI;
|
||||||
using AcDream.App.UI.Layout;
|
using AcDream.App.UI.Layout;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace AcDream.App.Tests.UI.Layout;
|
namespace AcDream.App.Tests.UI.Layout;
|
||||||
|
|
||||||
|
|
@ -634,20 +635,32 @@ public class CharacterStatControllerTests
|
||||||
|
|
||||||
ClickTab(layout, left: 92f);
|
ClickTab(layout, left: 92f);
|
||||||
|
|
||||||
var headers = list.Children
|
var headerPanels = list.Children
|
||||||
.Where(c => c is UiPanel and not UiClickablePanel)
|
.Where(c => c is UiPanel and not UiClickablePanel)
|
||||||
|
.Cast<UiPanel>()
|
||||||
|
.ToList();
|
||||||
|
var headers = headerPanels
|
||||||
.Select(c => c.Children.OfType<UiText>().First().LinesProvider()[0].Text)
|
.Select(c => c.Children.OfType<UiText>().First().LinesProvider()[0].Text)
|
||||||
.ToList();
|
.ToList();
|
||||||
Assert.Equal(new[]
|
Assert.Equal(new[]
|
||||||
{
|
{
|
||||||
"Specialized Skills",
|
"Specialized Skills",
|
||||||
"Trained Skills",
|
"Trained Skills",
|
||||||
"Useable Untrained Skills",
|
"Untrained Skills",
|
||||||
"Unuseable Untrained Skills",
|
"Unusable Skills",
|
||||||
}, headers);
|
}, headers);
|
||||||
|
Assert.Equal(new[] { 0x06000F90u, 0x06000F86u, 0x06000F98u, 0x06000F89u },
|
||||||
|
headerPanels.Select(h => h.BackgroundSprite).ToArray());
|
||||||
|
Assert.All(headerPanels, h =>
|
||||||
|
Assert.Equal(Vector4.One, h.Children.OfType<UiText>().First().LinesProvider()[0].Color));
|
||||||
|
|
||||||
var rows = list.Children.OfType<UiClickablePanel>().ToList();
|
var rows = list.Children.OfType<UiClickablePanel>().ToList();
|
||||||
Assert.Equal(12, rows.Count);
|
Assert.Equal(12, rows.Count);
|
||||||
|
Assert.All(rows, row =>
|
||||||
|
{
|
||||||
|
Assert.Equal(new Vector4(0f, 0f, 0f, 1f), row.BackgroundColor);
|
||||||
|
Assert.Equal(0u, row.BackgroundSprite);
|
||||||
|
});
|
||||||
var rowNames = rows
|
var rowNames = rows
|
||||||
.Select(r => r.Children.OfType<UiText>().ToList()[1].LinesProvider()[0].Text)
|
.Select(r => r.Children.OfType<UiText>().ToList()[1].LinesProvider()[0].Text)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
@ -658,6 +671,14 @@ public class CharacterStatControllerTests
|
||||||
"Healing", "Jump", "Loyalty", "Run",
|
"Healing", "Jump", "Loyalty", "Run",
|
||||||
"Alchemy", "Cooking", "Fletching",
|
"Alchemy", "Cooking", "Fletching",
|
||||||
}, rowNames);
|
}, rowNames);
|
||||||
|
|
||||||
|
var meleeTexts = rows[0].Children.OfType<UiText>().ToList();
|
||||||
|
Assert.Equal(Vector4.One, meleeTexts[1].LinesProvider()[0].Color);
|
||||||
|
Assert.Equal(new Vector4(0.55f, 1f, 0.55f, 1f), meleeTexts[2].LinesProvider()[0].Color);
|
||||||
|
|
||||||
|
var healingTexts = rows[5].Children.OfType<UiText>().ToList();
|
||||||
|
Assert.Equal(Vector4.One, healingTexts[1].LinesProvider()[0].Color);
|
||||||
|
Assert.Equal(Vector4.One, healingTexts[2].LinesProvider()[0].Color);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue