Files
TheriapolisV3/Theriapolis.Game/CodexUI/Widgets/CodexChip.cs
T

156 lines
6.8 KiB
C#
Raw Normal View History

using FontStashSharp;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Theriapolis.Game.CodexUI.Core;
namespace Theriapolis.Game.CodexUI.Widgets;
public enum ChipKind
{
Trait, // gild edge, italic serif, default for clade/species/feature traits
TraitDetriment,
SkillFromBg, // gild edge — sealed by background
SkillFromClass, // seal-red edge — picked from class options
Language, // mono-tag pill, ink border
BgFeature, // seal-red trait variant for the background card's feature row
}
/// <summary>
/// Pill-shaped chip used for traits, skills, languages, and background feature
/// names. Hovering surfaces a popover with the full description; clicking
/// fires <see cref="OnClick"/> for the few cases the screen needs (skill toggle).
/// </summary>
public sealed class CodexChip : CodexWidget
{
public string Text { get; set; }
public string PopoverTitle { get; set; }
public string PopoverBody { get; set; }
public string? PopoverTag { get; set; }
public ChipKind Kind { get; set; }
public System.Action? OnClick { get; set; }
private readonly CodexAtlas _atlas;
private readonly SpriteFontBase _font;
/// <summary>The screen sets this when the user hovers over us; the screen handles popover layout.</summary>
public bool IsHovered { get; private set; }
public CodexChip(string text, ChipKind kind, CodexAtlas atlas,
string popoverTitle = "", string popoverBody = "", string? popoverTag = null)
{
Text = text;
Kind = kind;
_atlas = atlas;
PopoverTitle = popoverTitle;
PopoverBody = popoverBody;
PopoverTag = popoverTag;
_font = kind == ChipKind.Language ? CodexFonts.MonoTagSmall : CodexFonts.SerifItalic;
}
protected override Point MeasureCore(Point available)
{
var s = _font.MeasureString(Text);
return new Point((int)s.X + CodexDensity.ChipPad * 3, (int)System.MathF.Ceiling(_font.LineHeight) + CodexDensity.ChipPad * 2);
}
protected override void ArrangeCore(Rectangle bounds) { }
public override void Update(GameTime gt, CodexInput input)
{
IsHovered = ContainsPoint(input.MousePosition);
if (IsHovered && input.LeftJustReleased) OnClick?.Invoke();
}
public override void Draw(SpriteBatch sb, GameTime gt)
{
var (fill, border, text) = GetColors();
if (IsHovered) fill = HoverShift(fill);
sb.Draw(_atlas.Pixel, Bounds, fill);
// 1-px outline.
sb.Draw(_atlas.Pixel, new Rectangle(Bounds.X, Bounds.Y, Bounds.Width, 1), border);
sb.Draw(_atlas.Pixel, new Rectangle(Bounds.X, Bounds.Bottom - 1, Bounds.Width, 1), border);
sb.Draw(_atlas.Pixel, new Rectangle(Bounds.X, Bounds.Y, 1, Bounds.Height), border);
sb.Draw(_atlas.Pixel, new Rectangle(Bounds.Right - 1, Bounds.Y, 1, Bounds.Height), border);
var s = _font.MeasureString(Text);
float tx = Bounds.X + (Bounds.Width - s.X) / 2f;
float ty = Bounds.Y + (Bounds.Height - _font.LineHeight) / 2f;
_font.DrawText(sb, Text, new Vector2(tx, ty), text);
}
private (Color fill, Color border, Color text) GetColors() => Kind switch
{
ChipKind.Trait => (Mix(CodexColors.Gild, CodexColors.Bg, 0.07f), Mix(CodexColors.Gild, CodexColors.Rule, 0.55f), CodexColors.Ink),
ChipKind.TraitDetriment => (Mix(CodexColors.Seal, CodexColors.Bg, 0.08f), Mix(CodexColors.Seal, CodexColors.Rule, 0.55f), CodexColors.Ink),
ChipKind.SkillFromBg => (Mix(CodexColors.Gild, CodexColors.Bg, 0.06f), Mix(CodexColors.Gild, CodexColors.Rule, 0.60f), CodexColors.Gild),
ChipKind.SkillFromClass => (Mix(CodexColors.Seal, CodexColors.Bg, 0.06f), Mix(CodexColors.Seal, CodexColors.Rule, 0.55f), CodexColors.Seal),
ChipKind.Language => (CodexColors.Bg, CodexColors.Rule, CodexColors.InkSoft),
ChipKind.BgFeature => (Mix(CodexColors.Seal, CodexColors.Bg, 0.08f), Mix(CodexColors.Seal, CodexColors.Rule, 0.55f), CodexColors.Seal),
_ => (CodexColors.Bg, CodexColors.Rule, CodexColors.Ink),
};
private static Color Mix(Color a, Color b, float t)
=> new(
(byte)(a.R * t + b.R * (1 - t)),
(byte)(a.G * t + b.G * (1 - t)),
(byte)(a.B * t + b.B * (1 - t)),
(byte)0xFF);
private static Color HoverShift(Color c)
=> new((byte)System.Math.Min(255, c.R + 14), (byte)System.Math.Min(255, c.G + 14), (byte)System.Math.Min(255, c.B + 14), c.A);
}
/// <summary>
/// Small +N / N pill that sits next to ability names. Visually a chip with
/// monospace text and seal-red (positive) or ink-mute (negative) chrome.
/// Hover surfaces a popover listing the contributing sources (clade, species).
/// </summary>
public sealed class CodexBonusPill : CodexWidget
{
public int Total { get; }
public string PopoverBody { get; set; } = "";
private readonly CodexAtlas _atlas;
private readonly SpriteFontBase _font;
public bool IsHovered { get; private set; }
public CodexBonusPill(int total, CodexAtlas atlas, string popoverBody = "")
{
Total = total;
_atlas = atlas;
PopoverBody = popoverBody;
_font = CodexFonts.MonoTag;
}
protected override Point MeasureCore(Point available)
{
string label = (Total >= 0 ? "+" : "") + Total.ToString();
var s = _font.MeasureString(label);
return new Point((int)s.X + 12, (int)System.MathF.Ceiling(_font.LineHeight) + 6);
}
protected override void ArrangeCore(Rectangle bounds) { }
public override void Update(GameTime gt, CodexInput input) => IsHovered = ContainsPoint(input.MousePosition);
public override void Draw(SpriteBatch sb, GameTime gt)
{
var border = Total >= 0 ? CodexColors.Seal : CodexColors.InkMute;
var fill = Total >= 0
? new Color(CodexColors.Seal.R, CodexColors.Seal.G, CodexColors.Seal.B, (byte)(IsHovered ? 36 : 18))
: new Color(CodexColors.InkMute.R, CodexColors.InkMute.G, CodexColors.InkMute.B, (byte)(IsHovered ? 28 : 14));
sb.Draw(_atlas.Pixel, Bounds, fill);
sb.Draw(_atlas.Pixel, new Rectangle(Bounds.X, Bounds.Y, Bounds.Width, 1), border);
sb.Draw(_atlas.Pixel, new Rectangle(Bounds.X, Bounds.Bottom - 1, Bounds.Width, 1), border);
sb.Draw(_atlas.Pixel, new Rectangle(Bounds.X, Bounds.Y, 1, Bounds.Height), border);
sb.Draw(_atlas.Pixel, new Rectangle(Bounds.Right - 1, Bounds.Y, 1, Bounds.Height), border);
string label = (Total >= 0 ? "+" : "") + Total.ToString();
var s = _font.MeasureString(label);
float tx = Bounds.X + (Bounds.Width - s.X) / 2f;
float ty = Bounds.Y + (Bounds.Height - _font.LineHeight) / 2f;
_font.DrawText(sb, label, new Vector2(tx, ty), border);
}
}