feat(gestalt-ui): 将 Gestalt Gizmo 重构为原版机械师风格
- 重写 GestaltBandwidthGizmo,改为原版带宽卡样式(136x75) - 使用方格带宽可视化:正常占用黄块,超载占用红块 - 统一带宽卡标题/数值布局与 tooltip 信息结构 - 重写 GestaltControlGroupGizmo 为原版控制组卡片风格 - 支持组标题点击全选、头像网格展示、悬停高亮与点击跳转 - 增加禁用态灰显与禁用原因提示,保留现有本地化 key
This commit is contained in:
Binary file not shown.
@@ -1,100 +1,121 @@
|
||||
using RimWorld;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public class GestaltBandwidthGizmo : Gizmo
|
||||
{
|
||||
private Pawn_GestaltTracker tracker;
|
||||
|
||||
private static readonly Color EmptyBlockColor = new Color(0.3f, 0.3f, 0.3f, 1f);
|
||||
private static readonly Color FilledBlockColor = ColorLibrary.Yellow;
|
||||
private static readonly Color ExcessBlockColor = ColorLibrary.Red;
|
||||
|
||||
private readonly Pawn_GestaltTracker tracker;
|
||||
|
||||
public GestaltBandwidthGizmo(Pawn_GestaltTracker tracker)
|
||||
{
|
||||
this.tracker = tracker;
|
||||
this.Order = -90f; // 放在最前面
|
||||
Order = -90f;
|
||||
}
|
||||
|
||||
public override bool Visible => tracker != null && tracker.Pawn != null && tracker.Pawn.IsGestaltNode(GestaltNodeType.OverlordNode);
|
||||
|
||||
public override float GetWidth(float maxWidth)
|
||||
{
|
||||
return 160f; // 稍微加宽以显示更多信息
|
||||
}
|
||||
|
||||
|
||||
public override bool Visible =>
|
||||
tracker != null &&
|
||||
tracker.Pawn != null &&
|
||||
tracker.Pawn.IsGestaltNode(GestaltNodeType.OverlordNode) &&
|
||||
Find.Selector.SelectedPawns.Count == 1;
|
||||
|
||||
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
|
||||
{
|
||||
Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f); // 增加高度以容纳超载信息
|
||||
Rect rect2 = rect.ContractedBy(6f);
|
||||
|
||||
// 背景
|
||||
GUI.color = parms.lowLight ? Command.LowLightBgColor : Color.white;
|
||||
GenUI.DrawTextureWithMaterial(rect, Command.BGTex, null);
|
||||
GUI.color = Color.white;
|
||||
|
||||
// 标题
|
||||
Text.Font = GameFont.Tiny;
|
||||
Text.Anchor = TextAnchor.UpperCenter;
|
||||
Widgets.Label(new Rect(rect2.x, rect2.y, rect2.width, 18f), "ARA_GestaltBandwidth".Translate());
|
||||
|
||||
// 带宽条
|
||||
Rect barRect = new Rect(rect2.x, rect2.y + 20f, rect2.width, 20f);
|
||||
float fillPercent = tracker.TotalBandwidth > 0 ?
|
||||
Mathf.Clamp01((float)tracker.UsedBandwidth / tracker.TotalBandwidth) :
|
||||
1f;
|
||||
|
||||
// 超载时使用红色,否则使用蓝色
|
||||
Color barColor = tracker.IsNetworkOverloaded() ?
|
||||
new Color(0.8f, 0.2f, 0.2f) : // 红色
|
||||
new Color(0.2f, 0.6f, 0.8f); // 蓝色
|
||||
|
||||
Widgets.FillableBar(barRect, fillPercent, SolidColorMaterials.NewSolidColorTexture(barColor));
|
||||
|
||||
// 带宽文本
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
string bandwidthText = tracker.IsNetworkOverloaded() ?
|
||||
$"{tracker.UsedBandwidth}/{tracker.TotalBandwidth} (+{tracker.NetworkOverload})" :
|
||||
$"{tracker.UsedBandwidth}/{tracker.TotalBandwidth}";
|
||||
|
||||
if (tracker.TotalBandwidth <= 0)
|
||||
{
|
||||
bandwidthText = $"{tracker.UsedBandwidth} (∞)";
|
||||
}
|
||||
|
||||
Widgets.Label(barRect, bandwidthText);
|
||||
|
||||
// 控制组数量
|
||||
Rect groupRect = new Rect(rect2.x, rect2.y + 45f, rect2.width, 20f);
|
||||
Text.Font = GameFont.Tiny;
|
||||
Text.Anchor = TextAnchor.MiddleLeft;
|
||||
string groupText = "ARA_GestaltGroup".Translate() + $": {tracker.ControlGroups.Count}/{tracker.TotalAvailableControlGroups}";
|
||||
Widgets.Label(groupRect, groupText);
|
||||
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
Text.Font = GameFont.Small;
|
||||
|
||||
// 鼠标悬停提示
|
||||
if (Mouse.IsOver(rect))
|
||||
{
|
||||
TooltipHandler.TipRegion(rect, () =>
|
||||
{
|
||||
string tip = "ARA_GestaltBandwidthTip".Translate(tracker.UsedBandwidth, tracker.TotalBandwidth);
|
||||
int availableBandwidth = Mathf.Max(0, tracker.TotalBandwidth - tracker.UsedBandwidth);
|
||||
tip += $"\n\n{"ARA_AvailableBandwidth".Translate(availableBandwidth)}";
|
||||
Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f);
|
||||
Rect innerRect = rect.ContractedBy(6f);
|
||||
|
||||
if (tracker.IsNetworkOverloaded())
|
||||
{
|
||||
tip += $"\n\n{"ARA_GestaltBandwidthExceeded".Translate()}".Colorize(Color.red);
|
||||
}
|
||||
|
||||
return tip;
|
||||
}, 7234342);
|
||||
|
||||
Widgets.DrawHighlight(rect);
|
||||
Widgets.DrawWindowBackground(rect);
|
||||
|
||||
int totalBandwidth = tracker.TotalBandwidth;
|
||||
int usedBandwidth = tracker.UsedBandwidth;
|
||||
string countText = usedBandwidth.ToString("F0") + " / " + totalBandwidth.ToString("F0");
|
||||
|
||||
string tip = "ARA_GestaltBandwidth".Translate().Colorize(ColoredText.TipSectionTitleColor) + ": " + countText + "\n\n" +
|
||||
"ARA_GestaltBandwidthTip".Translate(usedBandwidth, totalBandwidth);
|
||||
tip += "\n\n" + "ARA_AvailableBandwidth".Translate(Mathf.Max(0, totalBandwidth - usedBandwidth));
|
||||
tip += "\n" + "ARA_GestaltGroup".Translate() + ": " + tracker.ControlGroups.Count + "/" + tracker.TotalAvailableControlGroups;
|
||||
if (tracker.IsNetworkOverloaded())
|
||||
{
|
||||
tip += "\n\n" + "ARA_GestaltBandwidthExceeded".Translate().Colorize(ColorLibrary.RedReadable);
|
||||
}
|
||||
|
||||
TooltipHandler.TipRegion(rect, tip);
|
||||
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
Rect headerRect = new Rect(innerRect.x, innerRect.y, innerRect.width, 20f);
|
||||
Widgets.Label(headerRect, "ARA_GestaltBandwidth".Translate());
|
||||
Text.Anchor = TextAnchor.UpperRight;
|
||||
Widgets.Label(headerRect, countText);
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
int cellCount = Mathf.Max(usedBandwidth, totalBandwidth);
|
||||
Rect gridRect = new Rect(innerRect.x, headerRect.yMax + 6f, innerRect.width, innerRect.height - headerRect.height - 6f);
|
||||
|
||||
int rows = 2;
|
||||
int cellSize = Mathf.FloorToInt(gridRect.height / rows);
|
||||
int cols = cellSize > 0 ? Mathf.FloorToInt(gridRect.width / cellSize) : 0;
|
||||
|
||||
int failSafe = 0;
|
||||
while (rows * cols < cellCount)
|
||||
{
|
||||
rows++;
|
||||
cellSize = Mathf.FloorToInt(gridRect.height / rows);
|
||||
cols = cellSize > 0 ? Mathf.FloorToInt(gridRect.width / cellSize) : 0;
|
||||
failSafe++;
|
||||
|
||||
if (failSafe >= 1000)
|
||||
{
|
||||
Log.Error("Failed to fit gestalt bandwidth cells into gizmo rect.");
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
}
|
||||
}
|
||||
|
||||
if (cellSize <= 0 || cols <= 0)
|
||||
{
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
}
|
||||
|
||||
float xOffset = (gridRect.width - cols * cellSize) / 2f;
|
||||
int current = 0;
|
||||
for (int r = 0; r < rows; r++)
|
||||
{
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
current++;
|
||||
if (current > cellCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Rect cellRect = new Rect(
|
||||
gridRect.x + c * cellSize + xOffset,
|
||||
gridRect.y + r * cellSize,
|
||||
cellSize,
|
||||
cellSize).ContractedBy(2f);
|
||||
|
||||
if (current <= usedBandwidth)
|
||||
{
|
||||
Widgets.DrawRectFast(cellRect, current <= totalBandwidth ? FilledBlockColor : ExcessBlockColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
Widgets.DrawRectFast(cellRect, EmptyBlockColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
}
|
||||
|
||||
public override float GetWidth(float maxWidth)
|
||||
{
|
||||
return 136f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,204 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class GestaltControlGroupGizmo : Gizmo
|
||||
{
|
||||
private GestaltControlGroup controlGroup;
|
||||
private static readonly Color UncontrolledPawnBackgroundColor = new Color32(byte.MaxValue, 25, 25, 55);
|
||||
|
||||
public override bool Visible => controlGroup.AssignedPawns.Count > 0;
|
||||
public override float Order => -89f;
|
||||
private readonly GestaltControlGroup controlGroup;
|
||||
|
||||
public override bool Visible
|
||||
{
|
||||
get
|
||||
{
|
||||
if (controlGroup?.Tracker?.Pawn == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (controlGroup.AssignedPawns.Count <= 0)
|
||||
{
|
||||
return Find.Selector.SelectedPawns.Count == 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override float Order => controlGroup.AssignedPawns.Count > 0 ? -89f : -88f;
|
||||
|
||||
public GestaltControlGroupGizmo(GestaltControlGroup controlGroup)
|
||||
{
|
||||
this.controlGroup = controlGroup;
|
||||
Order = -89f;
|
||||
}
|
||||
|
||||
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
|
||||
{
|
||||
if (controlGroup?.Tracker == null)
|
||||
{
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
}
|
||||
|
||||
AcceptanceReport canControlPawns = controlGroup.Tracker.CanControlPawns;
|
||||
disabled = !canControlPawns;
|
||||
disabledReason = canControlPawns.Reason;
|
||||
|
||||
Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f);
|
||||
Rect innerRect = rect.ContractedBy(6f);
|
||||
bool mouseOver = Mouse.IsOver(innerRect);
|
||||
List<Pawn> assignedPawns = controlGroup.AssignedPawns;
|
||||
|
||||
// 背景
|
||||
Material material = (disabled || parms.lowLight || assignedPawns.Count <= 0) ? TexUI.GrayscaleGUI : null;
|
||||
GUI.color = parms.lowLight ? Command.LowLightBgColor : Color.white;
|
||||
GenUI.DrawTextureWithMaterial(rect, Command.BGTex, null);
|
||||
GenUI.DrawTextureWithMaterial(rect, parms.shrunk ? Command.BGTexShrunk : Command.BGTex, material);
|
||||
GUI.color = Color.white;
|
||||
|
||||
// 控制组编号
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
string label = "ARA_GestaltGroup".Translate();
|
||||
Widgets.Label(innerRect, label);
|
||||
|
||||
// 显示被控制的pawn
|
||||
Rect pawnRect = new Rect(innerRect.x, innerRect.y + 20f, innerRect.width, innerRect.height - 20f);
|
||||
TaggedString title = ("ARA_GestaltGroup".Translate() + " " + controlGroup.Index).Truncate(innerRect.width);
|
||||
Vector2 titleSize = Text.CalcSize(title);
|
||||
Rect titleRect = new Rect(innerRect.x, innerRect.y, titleSize.x, titleSize.y);
|
||||
Widgets.Label(titleRect, title);
|
||||
|
||||
if (Mouse.IsOver(titleRect))
|
||||
{
|
||||
Widgets.DrawHighlight(titleRect);
|
||||
if (Widgets.ButtonInvisible(titleRect))
|
||||
{
|
||||
Find.Selector.ClearSelection();
|
||||
for (int i = 0; i < assignedPawns.Count; i++)
|
||||
{
|
||||
Find.Selector.Select(assignedPawns[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Pawn> assignedPawns = controlGroup.AssignedPawns;
|
||||
if (assignedPawns.Count == 0)
|
||||
{
|
||||
GUI.color = ColoredText.SubtleGrayColor;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Widgets.Label(pawnRect, "(" + "ARA_NoGestaltPawns".Translate() + ")");
|
||||
Widgets.Label(innerRect, "(" + "ARA_NoGestaltPawns".Translate() + ")");
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
GUI.color = Color.white;
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
}
|
||||
|
||||
// 计算pawn图标布局
|
||||
float iconSize = Mathf.Min(pawnRect.width / assignedPawns.Count, pawnRect.height);
|
||||
float spacing = (pawnRect.width - (iconSize * assignedPawns.Count)) / (assignedPawns.Count + 1);
|
||||
Rect pawnsRect = new Rect(innerRect.x, innerRect.y + 26f + 4f, innerRect.width, innerRect.height - 26f - 4f);
|
||||
float cellSize = pawnsRect.height;
|
||||
int cols = 0;
|
||||
int rows = 0;
|
||||
|
||||
for (int i = 0; i < assignedPawns.Count; i++)
|
||||
for (float candidate = cellSize; candidate >= 1f; candidate -= 1f)
|
||||
{
|
||||
Rect iconRect = new Rect(
|
||||
pawnRect.x + spacing + (iconSize + spacing) * i,
|
||||
pawnRect.y + (pawnRect.height - iconSize) / 2,
|
||||
iconSize,
|
||||
iconSize
|
||||
);
|
||||
|
||||
Pawn pawn = assignedPawns[i];
|
||||
Widgets.ThingIcon(iconRect, pawn);
|
||||
|
||||
if (Mouse.IsOver(iconRect))
|
||||
cols = Mathf.FloorToInt(pawnsRect.width / candidate);
|
||||
rows = Mathf.FloorToInt(pawnsRect.height / candidate);
|
||||
if (cols * rows >= assignedPawns.Count)
|
||||
{
|
||||
Widgets.DrawHighlight(iconRect);
|
||||
TooltipHandler.TipRegion(iconRect, pawn.LabelCap);
|
||||
|
||||
if (Widgets.ButtonInvisible(iconRect))
|
||||
{
|
||||
CameraJumper.TryJumpAndSelect(pawn);
|
||||
}
|
||||
cellSize = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
if (cols <= 0 || rows <= 0)
|
||||
{
|
||||
return new GizmoResult(mouseOver ? GizmoState.Mouseover : GizmoState.Clear);
|
||||
}
|
||||
|
||||
float xOffset = (pawnsRect.width - cols * cellSize) / 2f;
|
||||
float yOffset = (pawnsRect.height - rows * cellSize) / 2f;
|
||||
|
||||
int index = 0;
|
||||
for (int r = 0; r < rows; r++)
|
||||
{
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
if (index >= assignedPawns.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Rect pawnRect = new Rect(
|
||||
pawnsRect.x + c * cellSize + xOffset,
|
||||
pawnsRect.y + r * cellSize + yOffset,
|
||||
cellSize,
|
||||
cellSize);
|
||||
|
||||
Pawn pawn = assignedPawns[index];
|
||||
RenderTexture portrait = PortraitsCache.Get(
|
||||
pawn,
|
||||
pawnRect.size,
|
||||
Rot4.East,
|
||||
default(Vector3),
|
||||
pawn.kindDef.controlGroupPortraitZoom);
|
||||
|
||||
if (!controlGroup.Tracker.ControlledPawns.Contains(pawn))
|
||||
{
|
||||
Widgets.DrawRectFast(pawnRect, UncontrolledPawnBackgroundColor);
|
||||
}
|
||||
|
||||
GUI.DrawTexture(pawnRect, portrait);
|
||||
|
||||
if (Mouse.IsOver(pawnRect))
|
||||
{
|
||||
Widgets.DrawHighlight(pawnRect);
|
||||
MouseoverSounds.DoRegion(pawnRect, SoundDefOf.Mouseover_Command);
|
||||
|
||||
if (Event.current.type == EventType.MouseDown)
|
||||
{
|
||||
if (Event.current.shift)
|
||||
{
|
||||
Find.Selector.Select(pawn);
|
||||
}
|
||||
else
|
||||
{
|
||||
CameraJumper.TryJumpAndSelect(pawn);
|
||||
}
|
||||
}
|
||||
|
||||
TargetHighlighter.Highlight(pawn, arrow: true, colonistBar: false);
|
||||
}
|
||||
|
||||
if (Find.Selector.IsSelected(pawn))
|
||||
{
|
||||
SelectionDrawerUtility.DrawSelectionOverlayOnGUI(pawn, pawnRect, 0.8f / cols, 20f);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index >= assignedPawns.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Find.WindowStack.FloatMenu == null)
|
||||
{
|
||||
TooltipHandler.TipRegion(rect, () =>
|
||||
{
|
||||
string tip = ("ARA_GestaltGroup".Translate() + " #" + controlGroup.Index).Colorize(ColoredText.TipSectionTitleColor) + "\n\n";
|
||||
IEnumerable<string> entries = assignedPawns.Select(p =>
|
||||
p.LabelCap + " (+" + p.GetStatValue(ARA_StatDefOf.ARA_GestaltBandwidthCost).ToString("0.#") + ")");
|
||||
tip += "AssignedPawns".Translate().Colorize(ColoredText.TipSectionTitleColor) + "\n" + entries.ToLineList(" - ");
|
||||
|
||||
if (disabled && !disabledReason.NullOrEmpty())
|
||||
{
|
||||
tip += "\n\n" + ("DisabledCommand".Translate() + ": " + disabledReason).Colorize(ColorLibrary.RedReadable);
|
||||
}
|
||||
|
||||
return tip;
|
||||
}, controlGroup.Index * 137 + 548233);
|
||||
}
|
||||
|
||||
return new GizmoResult(mouseOver ? GizmoState.Mouseover : GizmoState.Clear);
|
||||
}
|
||||
|
||||
public override float GetWidth(float maxWidth)
|
||||
@@ -84,4 +206,4 @@ namespace ArachnaeSwarm
|
||||
return 130f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user