PKHeX/PKHeX.WinForms/Controls/Slots/PokePreview.cs
Kurt f730f7d19a
Feature: Localization of Battle Templates (Showdown Set) (#4482)
* Localization capability for each language, import & export
* Lines with stat names (IVs/EVs) can be configured to many representations (X/X/X/X/X/X, HABCDS, etc).
* Add nonstandard localizations
* Add token types for Showdown's new set format
* Add new program settings for hover & export styles. Allows users to select which presentation format they want for the hover previews, as well as the set export format.
* Revises preview hover GUI to use new settings
* Revises export events to use new settings
* Moves no longer indicate end of set
* Enhance robustness of stat parsing
* Expand all settings in settings editor on form load
* Extract clipboard -> sets operation to api for maintainability & reusability
2025-05-01 23:16:36 -05:00

221 lines
8.1 KiB
C#

using System;
using System.Drawing;
using System.Windows.Forms;
using PKHeX.Core;
namespace PKHeX.WinForms.Controls;
public partial class PokePreview : Form
{
/// <summary> Minimum width to display the form. </summary>
private readonly int InitialWidth;
private readonly int InitialNameWidth;
private const int interiorMargin = 4; // 2x pixel border on each side
public PokePreview()
{
InitializeComponent();
InitialWidth = Width;
InitialNameWidth = L_Name.Width;
}
private static readonly Image[] GenderImages =
[
Properties.Resources.gender_0,
Properties.Resources.gender_1,
Properties.Resources.gender_2,
];
public void Populate(PKM pk, in BattleTemplateExportSettings settings)
{
var la = new LegalityAnalysis(pk);
int width = PopulateHeader(pk, settings);
PopulateMoves(pk, la, settings, ref width);
PopulateText(pk, la, settings, width);
}
private int PopulateHeader(PKM pk, in BattleTemplateExportSettings settings)
{
var name = GetNameTitle(pk, settings);
var size = MeasureSize(name, L_Name.Font);
L_Name.Width = Math.Max(InitialNameWidth, size.Width);
L_Name.Text = name;
PopulateBall(pk);
PopulateGender(pk);
var width = L_Name.Width + PB_Ball.Width + PB_Ball.Margin.Horizontal + PB_Gender.Width + PB_Gender.Margin.Horizontal + interiorMargin;
return Math.Max(InitialWidth, width);
}
private static string GetNameTitle(PKM pk, in BattleTemplateExportSettings settings)
{
// Don't care about form; the user will be able to see the sprite next to the preview.
var nick = pk.Nickname;
var strings = settings.Localization.Strings;
var all = strings.Species;
var species = pk.Species;
if (species >= all.Count)
return nick;
var expect = all[species];
if (settings.IsTokenInExport(BattleTemplateToken.Nickname))
return expect; // Nickname will be on another line.
if (nick.Equals(expect, StringComparison.OrdinalIgnoreCase))
return nick;
return $"{nick} ({expect})";
}
private void PopulateBall(PKM pk)
{
var ball = (byte)Ball.Poke;
if (pk.Format >= 3)
ball = pk.Ball;
PB_Ball.Image = Drawing.PokeSprite.SpriteUtil.GetBallSprite(ball);
}
private void PopulateGender(PKM pk)
{
if (pk.Format == 1)
{
PB_Gender.Image = null;
return;
}
var gender = pk.Gender;
if (gender >= GenderImages.Length)
gender = 2;
PB_Gender.Image = GenderImages[gender];
}
private void PopulateMoves(PKM pk, LegalityAnalysis la, in BattleTemplateExportSettings settings, ref int width)
{
var context = pk.Context;
var strings = settings.Localization.Strings;
var names = strings.movelist;
var check = la.Info.Moves;
var w1 = Move1.Populate(pk, strings, pk.Move1, context, names, check[0].Valid);
var w2 = Move2.Populate(pk, strings, pk.Move2, context, names, check[1].Valid);
var w3 = Move3.Populate(pk, strings, pk.Move3, context, names, check[2].Valid);
var w4 = Move4.Populate(pk, strings, pk.Move4, context, names, check[3].Valid);
var maxWidth = Math.Max(w1, Math.Max(w2, Math.Max(w3, w4)));
width = Math.Max(width, maxWidth + Move1.Margin.Horizontal + interiorMargin);
}
private void PopulateText(PKM pk, LegalityAnalysis la, in BattleTemplateExportSettings settings, int width)
{
var (before, after) = GetBeforeAndAfter(pk, la, settings);
var hover = Main.Settings.Hover;
bool hasMoves = pk.MoveCount != 0;
FLP_Moves.Visible = hasMoves;
var height = FLP_List.Top + interiorMargin;
if (hasMoves)
height += FLP_Moves.Height + FLP_Moves.Margin.Vertical;
ToggleLabel(L_LinesBeforeMoves, before, hover.PreviewShowPaste, ref width, ref height);
ToggleLabel(L_LinesAfterMoves, after, hover.HoverSlotShowEncounter, ref width, ref height);
Size = new Size(width, height);
}
private static void ToggleLabel(Control display, string text, bool visible, ref int width, ref int height)
{
if (!visible)
{
display.Visible = false;
return;
}
var size = MeasureSize(text, display.Font);
width = Math.Max(width, display.Margin.Horizontal + size.Width);
height += size.Height + display.Margin.Vertical;
display.Text = text;
display.Visible = true;
}
public static Size MeasureSize(ReadOnlySpan<char> text, Font font)
{
const TextFormatFlags flags = TextFormatFlags.LeftAndRightPadding | TextFormatFlags.VerticalCenter;
return TextRenderer.MeasureText(text, font, new Size(), flags);
}
private static (string Before, string After) GetBeforeAndAfter(PKM pk, LegalityAnalysis la, in BattleTemplateExportSettings settings)
{
var order = settings.Order;
// Bifurcate the order into two sections; split via Moves.
var moveIndex = settings.GetTokenIndex(BattleTemplateToken.Moves);
var before = moveIndex == -1 ? order : order[..moveIndex];
var after = moveIndex == -1 ? default : order[(moveIndex + 1)..];
if (before.Length > 0 && before[0] == BattleTemplateToken.FirstLine)
before = before[1..]; // remove first line token; trust that users don't randomly move it lower in the list.
var start = SummaryPreviewer.GetPreviewText(pk, settings with { Order = before });
var end = SummaryPreviewer.GetPreviewText(pk, settings with { Order = after });
if (settings.IsTokenInExport(BattleTemplateToken.IVs, before))
TryAppendOtherStats(pk, ref start, settings);
else if (settings.IsTokenInExport(BattleTemplateToken.IVs, after))
TryAppendOtherStats(pk, ref end, settings);
if (Main.Settings.Hover.HoverSlotShowEncounter)
end = SummaryPreviewer.AppendEncounterInfo(la, end);
return (start, end);
}
private static void TryAppendOtherStats(PKM pk, ref string line, in BattleTemplateExportSettings settings)
{
if (pk is IGanbaru g)
AppendGanbaru(g, ref line, settings);
if (pk is IAwakened a)
AppendAwakening(a, ref line, settings);
}
private static void AppendGanbaru(IGanbaru g, ref string line, in BattleTemplateExportSettings settings)
{
Span<byte> gvs = stackalloc byte[6];
g.GetGVs(gvs);
var statNames = settings.Localization.Config.GetStatDisplay(settings.StatsOther);
var value = TryAdd<byte>(gvs, statNames);
if (value.Length == 0)
return;
var result = settings.Localization.Config.Push(BattleTemplateToken.GVs, value);
line += Environment.NewLine + result;
}
private static void AppendAwakening(IAwakened a, ref string line, in BattleTemplateExportSettings settings)
{
Span<byte> avs = stackalloc byte[6];
a.GetAVs(avs);
var statNames = settings.Localization.Config.GetStatDisplay(settings.StatsOther);
var value = TryAdd<byte>(avs, statNames);
if (value.Length == 0)
return;
var result = settings.Localization.Config.Push(BattleTemplateToken.AVs, value);
line += Environment.NewLine + result;
}
private static string TryAdd<T>(ReadOnlySpan<T> stats, StatDisplayConfig statNames, T ignore = default) where T : unmanaged, IEquatable<T>
{
var chunks = ShowdownSet.GetStringStats(stats, ignore, statNames);
if (chunks.Length == 0)
return string.Empty;
return string.Join(" / ", chunks);
}
/// <summary> Prevent stealing focus from the form that shows this. </summary>
protected override bool ShowWithoutActivation => true;
private const int WS_EX_TOPMOST = 0x00000008;
protected override CreateParams CreateParams
{
get
{
CreateParams createParams = base.CreateParams;
createParams.ExStyle |= WS_EX_TOPMOST;
return createParams;
}
}
}