using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Windows; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Interop; using FontAwesome.Sharp; using PKHeX.Drawing.PokeSprite; using pkNX.Game; using pkNX.Structures; using pkNX.WinForms.Controls; using Button = System.Windows.Controls.Button; using Clipboard = System.Windows.Clipboard; using DataFormats = System.Windows.DataFormats; using DragDropEffects = System.Windows.DragDropEffects; namespace pkNX.WinForms; public readonly struct EditorButtonData { public static readonly int Width = 140; public static readonly int Height = 58; public static readonly Thickness Margin = new(3, 3, 3, 3); public static readonly int LayoutWidth = Width + (int)Margin.Left + (int)Margin.Right; public static readonly int LayoutHeight = Height + (int)Margin.Top + (int)Margin.Bottom; public string Title { get; init; } public IconChar? Icon { get; init; } public Action OnClick { get; init; } } /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow { public static readonly DependencyProperty CategoriesProperty = DependencyProperty.Register( nameof(Categories), typeof(EditorButtonData[]), typeof(MainWindow), new PropertyMetadata(Array.Empty())); private static readonly string[] SupportedLanguages = [ "カタカナ", "漢字", "English", "Français", "Italiano", "Deutsch", "Español", "한국", "汉字简化方案", "漢字簡化方案", ]; public EditorButtonData[] Categories { get => (EditorButtonData[])GetValue(CategoriesProperty); set => SetValue(CategoriesProperty, value); } public ProgramSettings Settings { get; } private new int Language { get => CB_Lang.SelectedIndex; set => CB_Lang.SelectedIndex = value; } private EditorBase? Editor; public MainWindow() { InitializeComponent(); DataContext = this; CB_Lang.ItemsSource = SupportedLanguages; SpriteName.AllowShinySprite = true; SpriteBuilderUtil.SpriterPreference = SpriteBuilderPreference.ForceSprites; Settings = ProgramSettings.LoadSettings(); CB_Lang.SelectedIndex = Settings.Language; Menu_DisplayAdvanced.IsChecked = Settings.DisplayAdvanced; if (!string.IsNullOrWhiteSpace(Settings.GamePath) && Directory.Exists(Settings.GamePath)) OpenPath(Settings.GamePath, Settings.GameOverride); Drop += (s, e) => { var files = (string[]?)e.Data?.GetData(DataFormats.FileDrop); if (files is null) return; foreach (var f in files) OpenPath(f); }; DragEnter += (s, e) => { if (e.Data?.GetDataPresent(DataFormats.FileDrop) ?? false) e.Effects = DragDropEffects.Copy; }; } private void ChangeLanguage(object sender, EventArgs e) { if (Editor == null) return; if (Editor.Game.GetGeneration() < 7 && Language > 7 && !GameVersion.GG.Contains(Editor.Game)) { WinFormsUtil.Alert("Selected Language is not available for this game", "Defaulting to English."); CB_Lang.SelectedIndex = 2; return; } Editor.Language = Language; } private static string GetClipboardTextString() { try { return Clipboard.GetText(); } catch (Exception ex) { Debug.WriteLine($"Unable to read clipboard text: {ex.Message}"); return string.Empty; } } private void OpenPath(string path, GameVersion gameOverride = GameVersion.Any) { try { if (Directory.Exists(path)) OpenFolder(path, gameOverride); else OpenFile(path); } catch (Exception ex) when (!Debugger.IsAttached) { WinFormsUtil.Error($"Failed to open -- {path}", ex.Message); } } private static void OpenFile(string path) { var result = FileRipper.TryOpenFile(path); if (result.Code != RipResultCode.Success || result.ResultPath is not { } resultPath) { WinFormsUtil.Alert("Invalid file loaded." + Environment.NewLine + $"Unable to recognize data: {result.Code}.", path); return; } System.Media.SystemSounds.Asterisk.Play(); Process.Start("explorer.exe", resultPath); } private void OpenFolder(string path, GameVersion gameOverride) { var loadResult = EditorBase.GetEditor(path, Language, gameOverride); var (editor, result) = loadResult; while (result == GameLoadResult.RomfsSelected) { var errorMsg = result.GetErrorMsg(); var parentPath = Directory.GetParent(path)?.FullName ?? string.Empty; if (string.IsNullOrEmpty(parentPath)) return; var prompt = $"Clicking 'OK' will allow pkNX to create a working directory in the following folder:\n\"{parentPath}\""; if (WinFormsUtil.Prompt(MessageBoxButton.OKCancel, errorMsg, prompt) != MessageBoxResult.OK) return; (editor, result) = EditorBase.GetEditor(parentPath, Language, gameOverride); } if (editor == null) { var msg = "An error occurred while loading the game files." + Environment.NewLine + result.GetErrorMsg(); WinFormsUtil.Alert(msg, path); return; } try { editor.Initialize(); LoadROM(editor); } catch (Exception ex) when (!Debugger.IsAttached) { var msg = "Failed to initialize ROM data." + Environment.NewLine + "Please ensure your dump is correctly set up, with updated patches merged in (if applicable)."; var stack = ex.StackTrace ?? string.Empty; WinFormsUtil.Error(msg, ex.Message, stack); } } private void LoadEditorButtons(EditorCategory category = EditorCategory.None) { List categories = []; if (category == EditorCategory.None) { foreach (var c in (EditorCategory[])Enum.GetValues(typeof(EditorCategory))) { if (c == EditorCategory.None) continue; if (Editor!.CountControlsForCategory(c) == 0) continue; categories.Add(new() { Icon = c.GetIcon(), Title = c.ToString(), OnClick = (_, _) => LoadEditorButtons(c), }); } } else { // Create back button categories.Add(new() { Title = "Back", Icon = IconChar.Reply, OnClick = (_, _) => LoadEditorButtons(), }); } categories.AddRange(Editor!.GetControls(category, Settings.DisplayAdvanced).OrderBy(x => x.Title)); Categories = [.. categories]; AdjustWindowSize(); CenterWindowOnScreen(); } private void CenterWindowOnScreen() { var helper = new WindowInteropHelper(this); var screen = Screen.FromHandle(helper.Handle); var area = screen.WorkingArea; var source = PresentationSource.FromVisual(this); var dpi = source?.CompositionTarget?.TransformFromDevice.M11 ?? 1.0; Left = (dpi * area.Left) + (((dpi * area.Width) - Width) / 2); Top = (dpi * area.Top) + (((dpi * area.Height) - Height) / 2); } private void AdjustWindowSize() { int wp = EditorButtonData.LayoutWidth; int hp = EditorButtonData.LayoutHeight; int area = wp * hp; var count = Categories.Length; // Resize form dimensions then center to screen, so that all buttons are shown. var totalArea = count * area; var squareSide = Math.Sqrt(totalArea); var columns = (int)Math.Ceiling(squareSide / wp) + 1; var rows = (count / columns) + 1; double containerHorizontalMargin = SV_Controls.Padding.Left + SV_Controls.Padding.Right; double containerVerticalMargin = SV_Controls.Padding.Top + SV_Controls.Padding.Bottom; double titleHeight = (SystemParameters.WindowCaptionHeight + SystemParameters.ResizeFrameHorizontalBorderHeight) * 1.5; double verticalBorderWidth = Math.Ceiling((SystemParameters.ResizeFrameVerticalBorderWidth + SystemParameters.FixedFrameVerticalBorderWidth) * 2) + 4; Width = containerHorizontalMargin + (columns * wp) + verticalBorderWidth; Height = SP_Header.Height + containerVerticalMargin + (rows * hp) + titleHeight; UpdateLayout(); // Adjust for scrollbar width //if (SV_Controls.ComputedVerticalScrollBarVisibility == Visibility.Visible) // DP_Content.Width += (int)Math.Ceiling(SystemParameters.VerticalScrollBarWidth); } private void LoadROM(EditorBase editor) { Editor?.Close(); // Clean exit prior editor Editor = editor; LoadEditorButtons(); Title = $"{nameof(pkNX)} - {Editor.Game}"; L_Path.Content = Editor.Location; Menu_Current.IsEnabled = true; EditUtil.LoadSettings(Editor.Game); EditUtil.SaveSettings(Editor.Game); System.Media.SystemSounds.Asterisk.Play(); } private async void Window_Closing(object sender, CancelEventArgs e) { if (Editor == null) return; Editor.Close(); EditUtil.SaveSettings(Editor.Game); Settings.Language = CB_Lang.SelectedIndex; Settings.GamePath = (string)L_Path.Content; Settings.GameOverride = Editor.Game; await ProgramSettings.SaveSettings(Settings); } private void Menu_Current_Click(object sender, EventArgs e) { if (Directory.Exists((string)L_Path.Content)) Process.Start("explorer.exe", (string)L_Path.Content); } private void Menu_SetRNGSeed_Click(object sender, EventArgs e) { var result = WinFormsUtil.Prompt(MessageBoxButton.YesNo, "Reseed RNG?", "If yes, copy the 32 bit (not hex) integer seed to the clipboard before hitting Yes."); if (MessageBoxResult.Yes != result) return; string val = GetClipboardTextString(); if (int.TryParse(val, out int seed)) { Util.Rand = new Random(seed); WinFormsUtil.Alert($"Reseeded RNG to seed: {seed}"); return; } WinFormsUtil.Alert("Unable to set seed."); } private void OpenCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { using FolderBrowserDialog fbd = new(); fbd.Description = "Select a folder containing the game files."; fbd.UseDescriptionForTitle = true; fbd.ShowNewFolderButton = false; if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK) OpenPath(fbd.SelectedPath); } private void SaveCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { Editor?.Save(); } private void ExitCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { //if (ModifierKeys == Key.Control) // triggered via hotkey { if (MessageBoxResult.Yes != WinFormsUtil.Prompt(MessageBoxButton.YesNo, $"Quit {nameof(pkNX)}?")) return; } Close(); } private void Button_Click(object sender, RoutedEventArgs e) { EditorButtonData data = (EditorButtonData)(sender as Button)!.DataContext; data.OnClick.Invoke(sender, e); } private async void Menu_DisplayAdvanced_Click(object sender, RoutedEventArgs e) { Menu_DisplayAdvanced.IsChecked = !Menu_DisplayAdvanced.IsChecked; Settings.DisplayAdvanced = Menu_DisplayAdvanced.IsChecked; await ProgramSettings.SaveSettings(Settings); // Force reload of editor buttons LoadEditorButtons(); } }