diff --git a/PKHeX.Core/Editing/Showdown/ShowdownSet.cs b/PKHeX.Core/Editing/Showdown/ShowdownSet.cs index 4659ca736..eb5661544 100644 --- a/PKHeX.Core/Editing/Showdown/ShowdownSet.cs +++ b/PKHeX.Core/Editing/Showdown/ShowdownSet.cs @@ -425,7 +425,7 @@ private string GetSpeciesNickname(string specForm) return $"{Nickname} ({specForm})"; } - private static string[] GetStringStats(ReadOnlySpan stats, int ignoreValue) + public static string[] GetStringStats(ReadOnlySpan stats, T ignoreValue) where T : IEquatable { var count = stats.Length - stats.Count(ignoreValue); if (count == 0) @@ -437,7 +437,7 @@ private static string[] GetStringStats(ReadOnlySpan stats, int ignoreValue) { var statIndex = GetStatIndexStored(i); var statValue = stats[statIndex]; - if (statValue == ignoreValue) + if (statValue.Equals(ignoreValue)) continue; // ignore unused stats var statName = StatNames[statIndex]; result[ctr++] = $"{statValue} {statName}"; diff --git a/PKHeX.Core/PKM/Interfaces/IGanbaru.cs b/PKHeX.Core/PKM/Interfaces/IGanbaru.cs index 62fc9e6ce..6aebafef8 100644 --- a/PKHeX.Core/PKM/Interfaces/IGanbaru.cs +++ b/PKHeX.Core/PKM/Interfaces/IGanbaru.cs @@ -137,6 +137,23 @@ public static void ClearGanbaruValues(this IGanbaru g) _ => throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be between 0 and 5."), }; + /// + /// Loads the values and stores them in the provided . + /// + /// Pokémon to check. + /// Span to store values in. + public static void GetGVs(this IGanbaru pk, Span value) + { + if (value.Length != 6) + return; + value[0] = pk.GV_HP; + value[1] = pk.GV_ATK; + value[2] = pk.GV_DEF; + value[3] = pk.GV_SPE; + value[4] = pk.GV_SPA; + value[5] = pk.GV_SPD; + } + /// /// Checks if any of the values are below a reference's minimum value. /// diff --git a/PKHeX.WinForms/Controls/PKM Editor/MoveChoice.cs b/PKHeX.WinForms/Controls/PKM Editor/MoveChoice.cs index 85ae4b8c7..2f715f0a1 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/MoveChoice.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/MoveChoice.cs @@ -51,7 +51,7 @@ public void UpdateLegality(MoveResult move, PKM entity, int i) return; } PB_Triangle.Visible = true; - PB_Triangle.Image = MoveDisplay.GetMoveImage(!move.Valid, entity, i); + PB_Triangle.Image = MoveDisplayState.GetMoveImage(!move.Valid, entity, i); } public void HealPP(PKM pk) diff --git a/PKHeX.WinForms/Controls/PKM Editor/MoveDisplay.Designer.cs b/PKHeX.WinForms/Controls/PKM Editor/MoveDisplay.Designer.cs new file mode 100644 index 000000000..c0086ee02 --- /dev/null +++ b/PKHeX.WinForms/Controls/PKM Editor/MoveDisplay.Designer.cs @@ -0,0 +1,87 @@ +namespace PKHeX.WinForms.Controls +{ + partial class MoveDisplay + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + FLP_Move = new System.Windows.Forms.FlowLayoutPanel(); + PB_Type = new System.Windows.Forms.PictureBox(); + L_Move = new System.Windows.Forms.Label(); + FLP_Move.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)PB_Type).BeginInit(); + SuspendLayout(); + // + // FLP_Move + // + FLP_Move.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + FLP_Move.Controls.Add(PB_Type); + FLP_Move.Controls.Add(L_Move); + FLP_Move.Location = new System.Drawing.Point(0, 0); + FLP_Move.Name = "FLP_Move"; + FLP_Move.Size = new System.Drawing.Size(138, 24); + FLP_Move.TabIndex = 18; + // + // PB_Type + // + PB_Type.Location = new System.Drawing.Point(0, 0); + PB_Type.Margin = new System.Windows.Forms.Padding(0, 0, 2, 0); + PB_Type.Name = "PB_Type"; + PB_Type.Size = new System.Drawing.Size(24, 24); + PB_Type.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + PB_Type.TabIndex = 4; + PB_Type.TabStop = false; + // + // L_Move + // + L_Move.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + L_Move.Location = new System.Drawing.Point(26, 0); + L_Move.Margin = new System.Windows.Forms.Padding(0); + L_Move.Name = "L_Move"; + L_Move.Size = new System.Drawing.Size(112, 24); + L_Move.TabIndex = 79; + L_Move.Text = "Name"; + L_Move.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // MoveDisplay + // + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + AutoSize = true; + AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + Controls.Add(FLP_Move); + Name = "MoveDisplay"; + Size = new System.Drawing.Size(138, 24); + FLP_Move.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)PB_Type).EndInit(); + ResumeLayout(false); + } + + #endregion + private System.Windows.Forms.FlowLayoutPanel FLP_Move; + private System.Windows.Forms.PictureBox PB_Type; + private System.Windows.Forms.Label L_Move; + } +} diff --git a/PKHeX.WinForms/Controls/PKM Editor/MoveDisplay.cs b/PKHeX.WinForms/Controls/PKM Editor/MoveDisplay.cs new file mode 100644 index 000000000..9bd739db6 --- /dev/null +++ b/PKHeX.WinForms/Controls/PKM Editor/MoveDisplay.cs @@ -0,0 +1,44 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using PKHeX.Core; +using PKHeX.Drawing.Misc; + +namespace PKHeX.WinForms.Controls; + +public partial class MoveDisplay : UserControl +{ + public MoveDisplay() => InitializeComponent(); + + public int Populate(PKM pk, ushort move, EntityContext context, ReadOnlySpan moves, bool valid = true) + { + if (move == 0 || move >= moves.Length) + { + Visible = false; + return 0; + } + Visible = true; + + byte type = MoveInfo.GetType(move, context); + var name = moves[move]; + if (move == (int)Core.Move.HiddenPower && pk.Context is not EntityContext.Gen8a) + { + type = (byte)pk.HPType; + name = $"{name} ({GameInfo.Strings.types[type]}) [{pk.HPPower}]"; + } + + var size = PokePreview.MeasureSize(name, L_Move.Font); + var ctrlWidth = PB_Type.Width + PB_Type.Margin.Horizontal + size.Width + L_Move.Margin.Horizontal; + + PB_Type.Image = TypeSpriteUtil.GetTypeSpriteIcon(type); + L_Move.Text = name; + if (valid) + L_Move.ResetForeColor(); + else + L_Move.ForeColor = Color.Red; + L_Move.Width = size.Width; + Width = ctrlWidth; + + return ctrlWidth; + } +} diff --git a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs index 239883203..7c9e52dca 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs @@ -2238,7 +2238,7 @@ private void PB_MarkCured_Click(object sender, EventArgs e) } } -public static class MoveDisplay +public static class MoveDisplayState { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Bitmap? GetMoveImage(bool isIllegal, PKM pk, int index) diff --git a/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs b/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs index f2ded1381..c1bdca27a 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs @@ -111,7 +111,10 @@ public void DragEnter(object? sender, DragEventArgs e) public void MouseMove(object? sender, MouseEventArgs e) { if (!Drag.CanStartDrag) + { + Hover.UpdateMousePosition(e.Location); return; + } if (sender is not PictureBox pb) return; diff --git a/PKHeX.WinForms/Controls/Slots/PokePreview.Designer.cs b/PKHeX.WinForms/Controls/Slots/PokePreview.Designer.cs new file mode 100644 index 000000000..cdc5a76b9 --- /dev/null +++ b/PKHeX.WinForms/Controls/Slots/PokePreview.Designer.cs @@ -0,0 +1,260 @@ +namespace PKHeX.WinForms.Controls +{ + partial class PokePreview + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + PAN_All = new System.Windows.Forms.Panel(); + FLP_List = new System.Windows.Forms.FlowLayoutPanel(); + L_Stats = new System.Windows.Forms.Label(); + FLP_Moves = new System.Windows.Forms.FlowLayoutPanel(); + Move1 = new MoveDisplay(); + Move2 = new MoveDisplay(); + Move3 = new MoveDisplay(); + Move4 = new MoveDisplay(); + L_Etc = new System.Windows.Forms.Label(); + PAN_Top = new System.Windows.Forms.Panel(); + FLP_Top = new System.Windows.Forms.FlowLayoutPanel(); + PB_Ball = new System.Windows.Forms.PictureBox(); + L_Name = new System.Windows.Forms.Label(); + PB_Gender = new System.Windows.Forms.PictureBox(); + PAN_All.SuspendLayout(); + FLP_List.SuspendLayout(); + FLP_Moves.SuspendLayout(); + PAN_Top.SuspendLayout(); + FLP_Top.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)PB_Ball).BeginInit(); + ((System.ComponentModel.ISupportInitialize)PB_Gender).BeginInit(); + SuspendLayout(); + // + // PAN_All + // + PAN_All.BackColor = System.Drawing.SystemColors.Window; + PAN_All.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + PAN_All.Controls.Add(FLP_List); + PAN_All.Controls.Add(PAN_Top); + PAN_All.Dock = System.Windows.Forms.DockStyle.Fill; + PAN_All.Location = new System.Drawing.Point(0, 0); + PAN_All.Margin = new System.Windows.Forms.Padding(0); + PAN_All.Name = "PAN_All"; + PAN_All.Size = new System.Drawing.Size(148, 180); + PAN_All.TabIndex = 19; + // + // FLP_List + // + FLP_List.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + FLP_List.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + FLP_List.Controls.Add(L_Stats); + FLP_List.Controls.Add(FLP_Moves); + FLP_List.Controls.Add(L_Etc); + FLP_List.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; + FLP_List.Location = new System.Drawing.Point(0, 34); + FLP_List.Margin = new System.Windows.Forms.Padding(0); + FLP_List.Name = "FLP_List"; + FLP_List.Size = new System.Drawing.Size(146, 144); + FLP_List.TabIndex = 1; + FLP_List.WrapContents = false; + // + // L_Stats + // + L_Stats.AutoSize = true; + L_Stats.Location = new System.Drawing.Point(2, 4); + L_Stats.Margin = new System.Windows.Forms.Padding(2, 4, 0, 0); + L_Stats.Name = "L_Stats"; + L_Stats.Size = new System.Drawing.Size(32, 15); + L_Stats.TabIndex = 5; + L_Stats.Text = "Stats"; + // + // FLP_Moves + // + FLP_Moves.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + FLP_Moves.AutoSize = true; + FLP_Moves.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + FLP_Moves.Controls.Add(Move1); + FLP_Moves.Controls.Add(Move2); + FLP_Moves.Controls.Add(Move3); + FLP_Moves.Controls.Add(Move4); + FLP_List.SetFlowBreak(FLP_Moves, true); + FLP_Moves.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; + FLP_Moves.Location = new System.Drawing.Point(0, 23); + FLP_Moves.Margin = new System.Windows.Forms.Padding(0, 4, 0, 4); + FLP_Moves.Name = "FLP_Moves"; + FLP_Moves.Size = new System.Drawing.Size(142, 96); + FLP_Moves.TabIndex = 7; + FLP_Moves.WrapContents = false; + // + // Move1 + // + Move1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Move1.AutoSize = true; + Move1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + Move1.Location = new System.Drawing.Point(4, 0); + Move1.Margin = new System.Windows.Forms.Padding(4, 0, 0, 0); + Move1.Name = "Move1"; + Move1.Size = new System.Drawing.Size(138, 24); + Move1.TabIndex = 1; + // + // Move2 + // + Move2.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Move2.AutoSize = true; + Move2.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + Move2.Location = new System.Drawing.Point(4, 24); + Move2.Margin = new System.Windows.Forms.Padding(4, 0, 0, 0); + Move2.Name = "Move2"; + Move2.Size = new System.Drawing.Size(138, 24); + Move2.TabIndex = 2; + // + // Move3 + // + Move3.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Move3.AutoSize = true; + Move3.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + Move3.Location = new System.Drawing.Point(4, 48); + Move3.Margin = new System.Windows.Forms.Padding(4, 0, 0, 0); + Move3.Name = "Move3"; + Move3.Size = new System.Drawing.Size(138, 24); + Move3.TabIndex = 3; + // + // Move4 + // + Move4.AutoSize = true; + Move4.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + Move4.Location = new System.Drawing.Point(4, 72); + Move4.Margin = new System.Windows.Forms.Padding(4, 0, 0, 0); + Move4.Name = "Move4"; + Move4.Size = new System.Drawing.Size(138, 24); + Move4.TabIndex = 4; + // + // L_Etc + // + L_Etc.AutoSize = true; + L_Etc.Location = new System.Drawing.Point(2, 123); + L_Etc.Margin = new System.Windows.Forms.Padding(2, 0, 0, 4); + L_Etc.Name = "L_Etc"; + L_Etc.Size = new System.Drawing.Size(28, 15); + L_Etc.TabIndex = 6; + L_Etc.Text = "Info"; + // + // PAN_Top + // + PAN_Top.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + PAN_Top.Controls.Add(FLP_Top); + PAN_Top.Dock = System.Windows.Forms.DockStyle.Top; + PAN_Top.Location = new System.Drawing.Point(0, 0); + PAN_Top.Margin = new System.Windows.Forms.Padding(0); + PAN_Top.Name = "PAN_Top"; + PAN_Top.Size = new System.Drawing.Size(146, 34); + PAN_Top.TabIndex = 0; + // + // FLP_Top + // + FLP_Top.Controls.Add(PB_Ball); + FLP_Top.Controls.Add(L_Name); + FLP_Top.Controls.Add(PB_Gender); + FLP_Top.Dock = System.Windows.Forms.DockStyle.Fill; + FLP_Top.Location = new System.Drawing.Point(0, 0); + FLP_Top.Margin = new System.Windows.Forms.Padding(0); + FLP_Top.Name = "FLP_Top"; + FLP_Top.Size = new System.Drawing.Size(144, 32); + FLP_Top.TabIndex = 71; + // + // PB_Ball + // + PB_Ball.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + PB_Ball.Location = new System.Drawing.Point(4, 4); + PB_Ball.Margin = new System.Windows.Forms.Padding(4, 4, 0, 0); + PB_Ball.Name = "PB_Ball"; + PB_Ball.Size = new System.Drawing.Size(24, 24); + PB_Ball.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + PB_Ball.TabIndex = 68; + PB_Ball.TabStop = false; + // + // L_Name + // + L_Name.Location = new System.Drawing.Point(28, 4); + L_Name.Margin = new System.Windows.Forms.Padding(0, 4, 0, 0); + L_Name.Name = "L_Name"; + L_Name.Size = new System.Drawing.Size(88, 24); + L_Name.TabIndex = 0; + L_Name.Text = "Species"; + L_Name.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // PB_Gender + // + PB_Gender.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + PB_Gender.Location = new System.Drawing.Point(116, 4); + PB_Gender.Margin = new System.Windows.Forms.Padding(0, 4, 4, 0); + PB_Gender.Name = "PB_Gender"; + PB_Gender.Size = new System.Drawing.Size(24, 24); + PB_Gender.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + PB_Gender.TabIndex = 70; + PB_Gender.TabStop = false; + // + // PokePreview + // + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(148, 180); + Controls.Add(PAN_All); + FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + MaximizeBox = false; + MinimizeBox = false; + Name = "PokePreview"; + ShowIcon = false; + ShowInTaskbar = false; + Text = "PokePreview"; + PAN_All.ResumeLayout(false); + FLP_List.ResumeLayout(false); + FLP_List.PerformLayout(); + FLP_Moves.ResumeLayout(false); + FLP_Moves.PerformLayout(); + PAN_Top.ResumeLayout(false); + FLP_Top.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)PB_Ball).EndInit(); + ((System.ComponentModel.ISupportInitialize)PB_Gender).EndInit(); + ResumeLayout(false); + } + + #endregion + + private System.Windows.Forms.Panel PAN_All; + private System.Windows.Forms.FlowLayoutPanel FLP_List; + private System.Windows.Forms.Label L_Stats; + private System.Windows.Forms.Panel PAN_Top; + private System.Windows.Forms.Label L_Name; + private System.Windows.Forms.PictureBox PB_Ball; + private System.Windows.Forms.PictureBox PB_Gender; + private MoveDisplay Move1; + private MoveDisplay Move2; + private MoveDisplay Move3; + private MoveDisplay Move4; + private System.Windows.Forms.Label L_Etc; + private System.Windows.Forms.FlowLayoutPanel FLP_Moves; + private System.Windows.Forms.FlowLayoutPanel FLP_Top; + } +} diff --git a/PKHeX.WinForms/Controls/Slots/PokePreview.cs b/PKHeX.WinForms/Controls/Slots/PokePreview.cs new file mode 100644 index 000000000..8aa6cee37 --- /dev/null +++ b/PKHeX.WinForms/Controls/Slots/PokePreview.cs @@ -0,0 +1,227 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using PKHeX.Core; + +namespace PKHeX.WinForms.Controls; + +public partial class PokePreview : Form +{ + /// Minimum width to display the form. + 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) + { + var la = new LegalityAnalysis(pk); + int width = PopulateHeader(pk); + PopulateMoves(pk, la, ref width); + PopulateText(pk, la, width); + } + + private int PopulateHeader(PKM pk) + { + var name = GetNameTitle(pk); + 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) + { + var nick = pk.Nickname; + var all = GameInfo.Strings.Species; + var species = pk.Species; + if (species >= all.Count) + return nick; + var expect = all[species]; + if (nick.Equals(expect, StringComparison.OrdinalIgnoreCase)) + return nick; + return $"{nick} ({expect})"; + } + + private void PopulateBall(PKM pk) + { + int ball = (int)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, ref int width) + { + var context = pk.Context; + var names = GameInfo.Strings.movelist; + var check = la.Info.Moves; + var w1 = Move1.Populate(pk, pk.Move1, context, names, check[0].Valid); + var w2 = Move2.Populate(pk, pk.Move2, context, names, check[1].Valid); + var w3 = Move3.Populate(pk, pk.Move3, context, names, check[2].Valid); + var w4 = Move4.Populate(pk, 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, int width) + { + var (stats, enc) = GetStatsString(pk, la); + var settings = 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_Stats, stats, settings.PreviewShowPaste, ref width, ref height); + ToggleLabel(L_Etc, enc, settings.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(string text, Font font) + { + const TextFormatFlags flags = TextFormatFlags.LeftAndRightPadding | TextFormatFlags.VerticalCenter; + return TextRenderer.MeasureText(text, font, new Size(), flags); + } + + private static (string Detail, string Encounter) GetStatsString(PKM pk, LegalityAnalysis la) + { + var setText = SummaryPreviewer.GetPreviewText(pk, la); + var sb = new StringBuilder(); + var lines = setText.AsSpan().EnumerateLines(); + if (!lines.MoveNext()) + throw new ArgumentException("Invalid text format", nameof(pk)); + + var first = lines.Current; + var itemIndex = first.IndexOf('@'); + if (itemIndex != -1) // Held Item + { + var remaining = first[(itemIndex + 2)..]; + if (remaining[^1] == ')') + remaining = remaining[..^3]; // lop off gender + var item = remaining.Trim(); + if (item.Length > 0) + sb.AppendLine($"Held Item: {item}"); + } + + if (pk is IGanbaru g) + AddGanbaru(g, sb); + if (pk is IAwakened a) + AddAwakening(a, sb); + + while (lines.MoveNext()) + { + var line = lines.Current; + if (IsMoveLine(line)) + { + while (lines.MoveNext()) + { + if (!IsMoveLine(lines.Current)) + break; + } + break; + } + sb.AppendLine(line.ToString()); + } + + var detail = sb.ToString(); + sb.Clear(); + while (lines.MoveNext()) + { + var line = lines.Current; + sb.AppendLine(line.ToString()); + } + var enc = sb.ToString(); + return (detail.TrimEnd(), enc.TrimEnd()); + + static bool IsMoveLine(ReadOnlySpan line) => line.Length != 0 && line[0] == '-'; + } + + private static void AddGanbaru(IGanbaru g, StringBuilder sb) + { + Span gvs = stackalloc byte[6]; + g.GetGVs(gvs); + TryAdd(sb, "GVs", gvs); + } + + private static void AddAwakening(IAwakened a, StringBuilder sb) + { + Span avs = stackalloc byte[6]; + a.GetAVs(avs); + TryAdd(sb, "AVs", avs); + } + + private static void TryAdd(StringBuilder sb, [ConstantExpected] string type, ReadOnlySpan stats, T ignore = default) where T : unmanaged, IEquatable + { + var chunks = ShowdownSet.GetStringStats(stats, ignore); + if (chunks.Length != 0) + sb.AppendLine($"{type}: {string.Join(" / ", chunks)}"); + } + + /// Prevent stealing focus from the form that shows this. + 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; + } + } +} diff --git a/PKHeX.WinForms/Controls/Slots/SlotHoverHandler.cs b/PKHeX.WinForms/Controls/Slots/SlotHoverHandler.cs index 3aa6eb5e4..4b5397ced 100644 --- a/PKHeX.WinForms/Controls/Slots/SlotHoverHandler.cs +++ b/PKHeX.WinForms/Controls/Slots/SlotHoverHandler.cs @@ -15,8 +15,7 @@ public sealed class SlotHoverHandler : IDisposable public DrawConfig Draw { private get; set; } = new(); public bool GlowHover { private get; set; } = true; - public static readonly CryPlayer CryPlayer = new(); - public static readonly SummaryPreviewer Preview = new(); + private readonly SummaryPreviewer Preview = new(); private static Bitmap Hover => SpriteUtil.Spriter.Hover; private readonly BitmapAnimator HoverWorker = new(); @@ -80,4 +79,9 @@ public void Dispose() Slot = null; Draw.Dispose(); } + + public void UpdateMousePosition(Point location) + { + Preview.UpdatePreviewPosition(location); + } } diff --git a/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs b/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs index dfceb858a..c8b2952fc 100644 --- a/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs +++ b/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Drawing; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; using PKHeX.Core; @@ -9,6 +12,9 @@ public sealed class SummaryPreviewer { private readonly ToolTip ShowSet = new() { InitialDelay = 200, IsBalloon = false, AutoPopDelay = 32_767 }; private readonly CryPlayer Cry = new(); + private readonly PokePreview Previewer = new(); + private CancellationTokenSource _source = new(); + private static HoverSettings Settings => Main.Settings.Hover; public void Show(Control pb, PKM pk) { @@ -18,19 +24,31 @@ public void Show(Control pb, PKM pk) return; } - if (Main.Settings.Hover.HoverSlotShowText) - { - var text = ShowdownParsing.GetLocalizedPreviewText(pk, Main.Settings.Startup.Language); - var la = new LegalityAnalysis(pk); - var result = new List { text, string.Empty }; - LegalityFormatting.AddEncounterInfo(la, result); - ShowSet.SetToolTip(pb, string.Join(Environment.NewLine, result)); - } - - if (Main.Settings.Hover.HoverSlotPlayCry) + if (Settings.HoverSlotShowPreview && Control.ModifierKeys != Keys.Alt) + UpdatePreview(pb, pk); + else if (Settings.HoverSlotShowText) + ShowSet.SetToolTip(pb, GetPreviewText(pk, new LegalityAnalysis(pk))); + if (Settings.HoverSlotPlayCry) Cry.PlayCry(pk, pk.Context); } + private void UpdatePreview(Control pb, PKM pk) + { + _source.Cancel(); + _source = new(); + UpdatePreviewPosition(new()); + Previewer.Populate(pk); + Previewer.Show(); + } + + public void UpdatePreviewPosition(Point location) + { + var cLoc = Cursor.Position; + var shift = Settings.PreviewCursorShift; + cLoc.Offset(shift); + Previewer.Location = cLoc; + } + public void Show(Control pb, IEncounterInfo enc) { if (enc.Species == 0) @@ -39,20 +57,38 @@ public void Show(Control pb, IEncounterInfo enc) return; } - if (Main.Settings.Hover.HoverSlotShowText) - { - var lines = enc.GetTextLines(GameInfo.Strings); - var text = string.Join(Environment.NewLine, lines); - ShowSet.SetToolTip(pb, text); - } - - if (Main.Settings.Hover.HoverSlotPlayCry) + if (Settings.HoverSlotShowText) + ShowSet.SetToolTip(pb, GetPreviewText(enc)); + if (Settings.HoverSlotPlayCry) Cry.PlayCry(enc, enc.Context); } public void Clear() { + var src = _source; + Task.Run(async () => + { + await Task.Delay(50, CancellationToken.None).ConfigureAwait(false); + if (!src.IsCancellationRequested) + Previewer.Invoke(Previewer.Hide); + }, src.Token); ShowSet.RemoveAll(); Cry.Stop(); } + + public static string GetPreviewText(PKM pk, LegalityAnalysis la) + { + var text = ShowdownParsing.GetLocalizedPreviewText(pk, Main.Settings.Startup.Language); + if (!Main.Settings.Hover.HoverSlotShowEncounter) + return text; + var result = new List { text, string.Empty }; + LegalityFormatting.AddEncounterInfo(la, result); + return string.Join(Environment.NewLine, result); + } + + private static string GetPreviewText(IEncounterInfo enc) + { + var lines = enc.GetTextLines(GameInfo.Strings); + return string.Join(Environment.NewLine, lines); + } } diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 633ac3e15..340393d8d 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -380,7 +380,7 @@ private static void ClosePopups() } } - private static bool IsPopupFormType(Form z) => z is not (Main or SplashScreen or SAV_FolderList); + private static bool IsPopupFormType(Form z) => z is not (Main or SplashScreen or SAV_FolderList or PokePreview); private void MainMenuSettings(object sender, EventArgs e) { diff --git a/PKHeX.WinForms/Properties/PKHeXSettings.cs b/PKHeX.WinForms/Properties/PKHeXSettings.cs index b0dacbf3c..38c08f2d1 100644 --- a/PKHeX.WinForms/Properties/PKHeXSettings.cs +++ b/PKHeX.WinForms/Properties/PKHeXSettings.cs @@ -317,6 +317,12 @@ public sealed class MysteryGiftDatabaseSettings [Serializable] public sealed class HoverSettings { + [LocalizedDescription("Show PKM Slot Preview on Hover")] + public bool HoverSlotShowPreview { get; set; } = true; + + [LocalizedDescription("Show Encounter Info in on Hover")] + public bool HoverSlotShowEncounter { get; set; } = true; + [LocalizedDescription("Show PKM Slot ToolTip on Hover")] public bool HoverSlotShowText { get; set; } = true; @@ -325,6 +331,12 @@ public sealed class HoverSettings [LocalizedDescription("Show a Glow effect around the PKM on Hover")] public bool HoverSlotGlowEdges { get; set; } = true; + + [LocalizedDescription("Show Showdown Paste in special Preview on Hover")] + public bool PreviewShowPaste { get; set; } = true; + + [LocalizedDescription("Show a Glow effect around the PKM on Hover")] + public Point PreviewCursorShift { get; set; } = new(16, 8); } [Serializable] diff --git a/PKHeX.WinForms/Subforms/SAV_Database.cs b/PKHeX.WinForms/Subforms/SAV_Database.cs index 21edac5af..42aa572e8 100644 --- a/PKHeX.WinForms/Subforms/SAV_Database.cs +++ b/PKHeX.WinForms/Subforms/SAV_Database.cs @@ -77,7 +77,11 @@ public SAV_Database(PKMEditor f1, SAVEditor saveditor) slot.ContextMenuStrip = mnu; if (Main.Settings.Hover.HoverSlotShowText) + { + slot.MouseMove += (o, args) => ShowSet.UpdatePreviewPosition(args.Location); slot.MouseEnter += (o, args) => ShowHoverTextForSlot(slot, args); + slot.MouseLeave += (o, args) => ShowSet.Clear(); + } slot.Enter += (sender, e) => { if (sender is not PictureBox pb) @@ -121,6 +125,7 @@ public SAV_Database(PKMEditor f1, SAVEditor saveditor) }; CB_Format.Items[0] = MsgAny; CenterToParent(); + Closing += (sender, e) => ShowSet.Clear(); } private readonly PictureBox[] PKXBOXES; @@ -615,8 +620,10 @@ private async void B_Search_Click(object sender, EventArgs e) private void UpdateScroll(object sender, ScrollEventArgs e) { - if (e.OldValue != e.NewValue) - FillPKXBoxes(e.NewValue); + if (e.OldValue == e.NewValue) + return; + FillPKXBoxes(e.NewValue); + ShowSet.Clear(); } private void SetResults(List res) @@ -688,8 +695,10 @@ protected override void OnMouseWheel(MouseEventArgs e) return; int oldval = SCR_Box.Value; int newval = oldval + (e.Delta < 0 ? 1 : -1); - if (newval >= SCR_Box.Minimum && SCR_Box.Maximum >= newval) - FillPKXBoxes(SCR_Box.Value = newval); + if (newval < SCR_Box.Minimum || SCR_Box.Maximum < newval) + return; + FillPKXBoxes(SCR_Box.Value = newval); + ShowSet.Clear(); } private void ChangeFormatFilter(object sender, EventArgs e) diff --git a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs index f9b150497..900bed02d 100644 --- a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs +++ b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs @@ -359,8 +359,10 @@ private void B_Search_Click(object sender, EventArgs e) private void UpdateScroll(object sender, ScrollEventArgs e) { - if (e.OldValue != e.NewValue) - FillPKXBoxes(e.NewValue); + if (e.OldValue == e.NewValue) + return; + FillPKXBoxes(e.NewValue); + ShowSet.Clear(); } private void SetResults(List res) @@ -369,7 +371,8 @@ private void SetResults(List res) ShowSet.Clear(); SCR_Box.Maximum = (int)Math.Ceiling((decimal)Results.Count / RES_MIN); - if (SCR_Box.Maximum > 0) SCR_Box.Maximum--; + if (SCR_Box.Maximum > 0) + SCR_Box.Maximum--; SCR_Box.Value = 0; FillPKXBoxes(0); @@ -428,8 +431,10 @@ protected override void OnMouseWheel(MouseEventArgs e) return; int oldval = SCR_Box.Value; int newval = oldval + (e.Delta < 0 ? 1 : -1); - if (newval >= SCR_Box.Minimum && SCR_Box.Maximum >= newval) - FillPKXBoxes(SCR_Box.Value = newval); + if (newval < SCR_Box.Minimum || SCR_Box.Maximum < newval) + return; + FillPKXBoxes(SCR_Box.Value = newval); + ShowSet.Clear(); } private void ChangeFormatFilter(object sender, EventArgs e) diff --git a/PKHeX.WinForms/Subforms/Save Editors/SAV_GroupViewer.cs b/PKHeX.WinForms/Subforms/Save Editors/SAV_GroupViewer.cs index c10c2cd03..be73df585 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/SAV_GroupViewer.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/SAV_GroupViewer.cs @@ -37,9 +37,12 @@ public SAV_GroupViewer(SaveFile sav, IPKMView view, IReadOnlyList gro foreach (PictureBox pb in Box.Entries) { pb.Click += (o, args) => OmniClick(pb, args); - pb.MouseHover += (o, args) => HoverSlot(pb, args); pb.ContextMenuStrip = mnu; + pb.MouseMove += (o, args) => Preview.UpdatePreviewPosition(args.Location); + pb.MouseEnter += (o, args) => HoverSlot(pb, args); + pb.MouseLeave += (o, args) => Preview.Clear(); } + Closing += (s, e) => Preview.Clear(); } private void HoverSlot(object sender, EventArgs e)