using System.ComponentModel; using System.IO; using System.Net; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Effects; using PCL.Core.App; using PCL.Core.App.IoC; using PCL.Core.App.Localization; using PCL.Core.Logging; using PCL.Core.Minecraft; using PCL.Core.UI; using PCL.Core.UI.Theme; using PCL.Core.Utils; using PCL.Core.Utils.OS; using PCL.Core.Utils.Validate; using PCL.Network; namespace PCL; public partial class FormMain { // 愚人节鼠标位置 public MouseEventArgs lastMouseArg; private void FormMain_MouseMove(object sender, MouseEventArgs e) { lastMouseArg = e; } #region 基础 // 更新日志 private void ShowUpdateLog() { ModBase.RunInNewThread(() => { var changelogFile = $"{ModBase.pathTemp}CEUpdateLog.md"; string changelog; if (File.Exists(changelogFile)) changelog = ModBase.ReadFile(changelogFile); else changelog = Lang.Text("Main.UpdateLog.Empty"); if (ModMain.MyMsgBoxMarkdown(changelog, Lang.Text("Main.UpdateLog.Title", ModBase.versionBranchName, ModBase.versionBaseName), Lang.Text("Common.Action.Confirm"), Lang.Text("Main.UpdateLog.FullChangelog")) == 2) ModBase.OpenWebsite("https://github.com/PCL-Community/PCL2-CE/releases"); }, "UpdateLog Output"); } // 窗口加载 private bool isWindowLoadFinished; private readonly DragHelper _helper = new(); public FormMain() { ModBase.applicationStartTick = TimeUtils.GetTimeTick(); // 刷新主题 // ThemeCheckAll(False) // ThemeRefreshColor() ThemeService.ColorModeChanged += (_, _) => ThemeManager.ThemeRefresh(); ThemeService.ColorThemeChanged += theme => ThemeManager.ThemeRefresh((int)theme); // 窗体参数初始化 ModMain.frmMain = this; ModMain.frmLaunchLeft = new PageLaunchLeft(); ModMain.frmLaunchRight = new PageLaunchRight(); // 版本号改变 var lastVersion = States.System.LastVersion; if (lastVersion < ModBase.versionCode) { // 重新询问是否启用遥测数据收集 if (lastVersion <= 511) { if (!Config.System.TelemetryConfig.IsDefault() && Config.System.Telemetry) { Config.System.TelemetryConfig.Reset(); ModBase.Log("[Start] 遥测策略变更:由旧版本升级到含新版遥测的版本,已重置遥测设置"); } } // 触发升级 UpgradeSub(lastVersion); } else if (lastVersion > ModBase.versionCode) // 触发降级 DowngradeSub(lastVersion); _ = Config.Preference.Theme.ThemeSelected; // 注册拖拽事件(不能直接加 Handles,否则没用;#6340) AddHandler(DragDrop.DragEnterEvent, new DragEventHandler(HandleDrag), true); AddHandler(DragDrop.DragOverEvent, new DragEventHandler(HandleDrag), true); // 注册 MsgBox 事件 MsgBoxWrapper.OnShow += ModMain.MsgBoxWrapper_OnShow; // 注册 Hint 事件 HintWrapper.OnShow += HintService.HintWrapper_OnShow; // 加载 UI InitializeComponent(); Opacity = 0d; try { Height = States.UI.WindowHeight; Width = States.UI.WindowWidth; } catch (Exception ex) // 修复 #2019 { ModBase.Log( ex, "读取窗口默认大小失败", ModBase.LogLevel.Hint, userSummary: Lang.Text("Main.Error.OperationFailed")); Height = MinHeight + 100d; Width = MinWidth + 100d; } // 管理员权限下文件拖拽 if (ProcessInterop.IsAdmin()) { ModBase.Log("[Start] PCL 当前正以管理员权限运行"); SourceInitialized += (_, _) => { var windowInterop = new WindowInteropHelper(this); _helper.HwndSource = HwndSource.FromHwnd(windowInterop.Handle); _helper.AddHook(); }; Closing += (_, _) => _helper.RemoveHook(); _helper.DragDrop += (_, _) => FileDrag(_helper.DropFilePaths); } if (ModMain.frmLaunchLeft.Parent is not null) ModMain.frmLaunchLeft.SetValue(ContentPresenter.ContentProperty, null); if (ModMain.frmLaunchRight.Parent is not null) ModMain.frmLaunchRight.SetValue(ContentPresenter.ContentProperty, null); PanMainLeft.Child = ModMain.frmLaunchLeft; pageLeft = ModMain.frmLaunchLeft; PanMainRight.Child = ModMain.frmLaunchRight; pageRight = ModMain.frmLaunchRight; ModMain.frmLaunchRight.PageState = MyPageRight.PageStates.ContentStay; // 调试模式提醒 if (ModBase.modeDebug) HintService.Hint(Lang.Text("Main.DebugMode.Hint")); // 尽早执行的加载池 ModFolder.mcFolderListLoader .Start(0); // 为了让下载已存在文件检测可以正常运行,必须跑一次;为了让启动按钮尽快可用,需要尽早执行;为了与 PageLaunchLeft 联动,需要为 0 而不是 GetUuid ModBase.Log("[Start] 第二阶段加载用时:" + (TimeUtils.GetTimeTick() - ModBase.applicationStartTick) + " ms"); // 注册生命周期状态事件 Lifecycle.When(LifecycleState.WindowCreated, FormMain_Loaded); } private void FormMain_Loaded() // (sender As Object, e As RoutedEventArgs) Handles Me.Loaded { FormMain_SizeChanged(); ModBase.applicationStartTick = TimeUtils.GetTimeTick(); ModBase.frmHandle = new WindowInteropHelper(this).Handle; // 读取设置 PageSetupUI.BackgroundRefresh(false, true); ModMusic.MusicRefreshPlay(false, true); // 扩展按钮 BtnExtraUpdateRestart.showCheck = BtnExtraUpdateRestart_ShowCheck; BtnExtraDownload.showCheck = BtnExtraDownload_ShowCheck; BtnExtraBack.showCheck = BtnExtraBack_ShowCheck; BtnExtraApril.showCheck = BtnExtraApril_ShowCheck; BtnExtraShutdown.showCheck = BtnExtraShutdown_ShowCheck; BtnExtraLog.showCheck = BtnExtraLog_ShowCheck; BtnExtraApril.ShowRefresh(); // 初始化尺寸改变 if (!Config.Preference.LockWindowSize) AddResizer(); else RemoveResizer(); // PLC 彩蛋 if (RandomUtils.NextInt(1, 1000) == 233) ShapeTitleLogo.Data = (Geometry)new GeometryConverter().ConvertFromString( "M26,29 v-25 h6 a7,7 180 0 1 0,14 h-6 M83,6.5 a10,11.5 180 1 0 0,18 M48,2.5 v24.5 h13.5"); // 加载窗口 ThemeManager.ThemeRefresh(); ModSetup.ApplyAll(); Lifecycle.CurrentApplication.Resources["BlurSamplingRate"] = Config.Preference.Blur.SamplingRate * 0.01d; Lifecycle.CurrentApplication.Resources["BlurType"] = Config.Preference.Blur.KernelType; if (Config.Preference.Blur.IsEnabled) Lifecycle.CurrentApplication.Resources["BlurRadius"] = Config.Preference.Blur.Radius * 1.0d; else Lifecycle.CurrentApplication.Resources["BlurRadius"] = 0.0d; // #If DEBUG Then // MinHeight = 50 // MinWidth = 50 // #End If Topmost = false; if (ModMain.frmStart is not null) ModMain.frmStart.Close(new TimeSpan(0, 0, 0, 0, (int)Math.Round(400d / ModAnimation.aniSpeed))); // 更改窗口 // Top = (GetWPFSize(My.Computer.Screen.WorkingArea.Height) - Height) / 2 // Left = (GetWPFSize(My.Computer.Screen.WorkingArea.Width) - Width) / 2 isSizeSaveable = true; ShowWindowToTop(); var hwndSource = (HwndSource)PresentationSource.FromVisual(this); hwndSource.AddHook(WndProc); ModAnimation.AniStart(new[] { ModAnimation.AaCode(() => ModAnimation.AniControlEnabled -= 1, 50), ModAnimation.AaOpacity(this, Config.Preference.Theme.WindowOpacity / 1000d + 0.4d, 250, 100), ModAnimation.AaDouble(i => TransformPos.Y += (double)i, -TransformPos.Y, 600, 100, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, -TransformRotate.Angle, 500, 100, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), ModAnimation.AaCode(() => { RenderTransform = null; isWindowLoadFinished = true; ModBase.Log( $"[System] DPI:{ModBase.dpi},系统版本:{Environment.OSVersion.VersionString},PCL 位置:{Basics.ExecutablePath}"); }, after: true) }, "Form Show"); // Timer 启动 ModAnimation.AniStart(); ModMain.TimerMainStart(); // 特殊版本提示 ModBase.RunInNewThread(() => { // 特殊版本提示 try { #if DEBUG || DEBUGCI if (Environment.GetEnvironmentVariable("PCL_DISABLE_DEBUG_HINT") is null) { #if DEBUG var hint = Lang.Text("Main.SpecialVersion.DebugHint"); #else var hint = Lang.Text("Main.SpecialVersion.CiHint"); #endif ModMain.MyMsgBox( $"{hint}{"\r\n"}{"\r\n"}{Lang.Text("Main.SpecialVersion.HideHintNotice")}", Lang.Text("Main.SpecialVersion.Title"), Lang.Text("Main.SpecialVersion.IUnderstand"), Lang.Text("Main.SpecialVersion.OpenDownloadPageAndExit"), isWarn: true, button2Action: () => { ModBase.OpenWebsite("https://github.com/PCL-Community/PCL2-CE/releases/latest"); EndProgram(false); }); } #endif // EULA 提示 if (!States.System.LauncherEula) switch (ModMain.MyMsgBox(Lang.Text("Main.Eula.Message"), Lang.Text("Main.Eula.Title"), Lang.Text("Common.Action.Agree"), Lang.Text("Common.Action.Decline"), Lang.Text("Main.Eula.View"), button3Action: () => ModBase.OpenWebsite("https://shimo.im/docs/rGrd8pY8xWkt6ryW"))) { case 1: { States.System.LauncherEula = true; break; } case 2: { EndProgram(false); break; } } // 遥测提示 if (Config.System.TelemetryConfig.IsDefault()) { var selection = ModMain.MyMsgBox( Lang.Text("Main.Telemetry.Message"), Lang.Text("Main.Telemetry.Title"), Lang.Text("Common.Action.Agree"), Lang.Text("Common.Action.Decline")); Config.System.TelemetryConfig.SetValue(selection == 1, forceNewValue: true); } // 启动加载器池 try { ModDownload.dlClientListMojangLoader.Start(1); // PCL 会同时根据这里的加载结果决定是否使用官方源进行下载 RunCountSub(); UpdateManager.serverLoader.Start(1); ModBase.RunInNewThread(ModMain.TryClearTaskTemp, "TryClearTaskTemp", ThreadPriority.BelowNormal); } catch (Exception ex) { ModBase.Log( ex, "初始化加载池运行失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Main.Error.OperationFailed")); } HardwareInfo.GetHardwareInfo(); } catch (Exception ex) { ModBase.Log( ex, "初始弹窗提示运行失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Main.Error.OperationFailed")); } }, "Start Loader", ThreadPriority.BelowNormal); ModBase.Log($"[Start] 第三阶段加载用时:{TimeUtils.GetTimeTick() - ModBase.applicationStartTick} ms"); } // 根据打开次数触发的事件 private void RunCountSub() { States.System.StartupCount += 1; } // 升级与降级事件 private void UpgradeSub(int lastVersionCode) { ModBase.Log("[Start] 版本号从 " + lastVersionCode + " 升高到 " + ModBase.versionCode); States.System.LastVersion = ModBase.versionCode; // 检查有记录的最高版本号 int lowerVersionCode; #if BETA lowerVersionCode = States.System.LastBetaVersion; if (lowerVersionCode < ModBase.versionCode) { States.System.LastBetaVersion = ModBase.versionCode; ModBase.Log($"[Start] 最高版本号从 {lowerVersionCode} 升高到 {ModBase.versionCode}"); } #else lowerVersionCode = States.System.LastAlphaVersion; if (lowerVersionCode < ModBase.versionCode) { States.System.LastAlphaVersion = ModBase.versionCode; ModBase.Log($"[Start] 最高版本号从 {lowerVersionCode} 升高到 {ModBase.versionCode}"); } #endif // 被移除的窗口设置选项 (Commit 3161488 2026/1/23) if ((int)Config.Launch.GameWindowMode == 5) Config.Launch.GameWindowMode = GameWindowSizeMode.Default; // 更新后展示社区版提示 UpdateManager.ShowCEAnnounce(); // 输出更新日志 if (lastVersionCode <= 0) return; if (lowerVersionCode >= ModBase.versionCode) return; ShowUpdateLog(); // 重置自定义主页配置 if (lastVersionCode < 521 && Config.Preference.Homepage.SelectedPreset >= 3) { Config.Preference.Homepage.SelectedPreset = 0; ModMain.MyMsgBox(Lang.Text("Main.HomepageReset.Content"), Lang.Text("Main.HomepageReset.Title")); } } private void DowngradeSub(int lastVersionCode) { ModBase.Log("[Start] 版本号从 " + lastVersionCode + " 降低到 " + ModBase.versionCode); States.System.LastVersion = ModBase.versionCode; } #endregion #region 自定义窗口 private bool canResize = true; // 重写窗口边缘判定以使 DWM 自带的 resizer 行为看起来比较正常 private nint _SizeWndProc(nint hWnd, int msg, nint wParam, nint lParam, ref bool handled) { // 窗口活动常量 const int WM_NCHITTEST = 0x84; const int HTCLIENT = 1; const int HTLEFT = 10; const int HTRIGHT = 11; const int HTTOP = 12; const int HTTOPLEFT = 13; const int HTTOPRIGHT = 14; const int HTBOTTOM = 15; const int HTBOTTOMLEFT = 16; const int HTBOTTOMRIGHT = 17; // WPF 尺寸的 offset const int offsetWpf = 6; const int hitWidthWpf = 5; // 过滤非 WM_NCHITTEST 事件 if (msg != WM_NCHITTEST) return nint.Zero; // 提取鼠标坐标 // 没妈的 VB 强转还得检查一下幻想的妈是不是还活着 var mouseBytes = BitConverter.GetBytes(lParam.ToInt64()); var xMouse = BitConverter.ToInt16(mouseBytes, 0); var yMouse = BitConverter.ToInt16(mouseBytes, 2); // 获取窗口参数 var windowRect = WindowInterop.GetWindowRectangle(hWnd); var windowBounds = windowRect.ToWindowBounds(); // 判断鼠标是否在窗口范围内 var isInWindow = xMouse >= windowRect.Left && xMouse <= windowRect.Right && yMouse >= windowRect.Top && yMouse <= windowRect.Bottom; // 过滤不在窗口内的请求 if (!isInWindow) return nint.Zero; // 如果 CanResize 为 False,直接返回 HTCLIENT if (!canResize) return new nint(HTCLIENT); // 真实像素尺寸的 offset var dpi = VisualTreeHelper.GetDpi(this); var offsetPxX = offsetWpf * dpi.DpiScaleX; var offsetPxY = offsetWpf * dpi.DpiScaleY; var hitWidthPxX = hitWidthWpf * dpi.DpiScaleX; var hitWidthPxY = hitWidthWpf * dpi.DpiScaleY; // 计算鼠标相对于窗口左上角的物理像素位置 var relX = xMouse - windowRect.Left; var relY = yMouse - windowRect.Top; var w = windowBounds.Width; var h = windowBounds.Height; // 判定是否命中偏移后的热区 var inLeft = relX >= offsetPxX && relX <= offsetPxX + hitWidthPxX; var inRight = relX <= w - offsetPxX && relX >= w - offsetPxX - hitWidthPxX; var inTop = relY >= offsetPxY && relY <= offsetPxY + hitWidthPxY; var inBottom = relY <= h - offsetPxY && relY >= h - offsetPxY - hitWidthPxY; handled = true; // 接管该区域的消息 // 返回结果 if (inTop && inLeft) return new nint(HTTOPLEFT); if (inTop && inRight) return new nint(HTTOPRIGHT); if (inBottom && inLeft) return new nint(HTBOTTOMLEFT); if (inBottom && inRight) return new nint(HTBOTTOMRIGHT); if (inLeft) return new nint(HTLEFT); if (inRight) return new nint(HTRIGHT); if (inTop) return new nint(HTTOP); if (inBottom) return new nint(HTBOTTOM); // 如果在 0-offset 范围内,返回 HTCLIENT 杀掉默认缩放 return new nint(HTCLIENT); } protected override void OnSourceInitialized(EventArgs e) { // 硬件加速 if (Config.System.DisableHardwareAcceleration) { var hwndSource = PresentationSource.FromVisual(this) as HwndSource; if (hwndSource is not null) hwndSource.CompositionTarget.RenderMode = RenderMode.SoftwareOnly; } base.OnSourceInitialized(e); // 获取当前窗口句柄 var hwnd = new WindowInteropHelper(this).Handle; var source = HwndSource.FromHwnd(hwnd); if (source is not null) { // 渲染层允许 Alpha 通道通过 source.CompositionTarget.BackgroundColor = Colors.Transparent; // 魔改窗口边缘判定 source.AddHook(_SizeWndProc); } // 设置 DWM 窗口框架 try { WindowInterop.ExtendFrameIntoClientArea(hwnd, -1); } catch (Exception ex) { LogWrapper.Error("DWM 窗口框架应用失败: " + ex.Message); } } // 关闭 private void FormMain_Closing(object sender, CancelEventArgs e) { EndProgram(true); e.Cancel = true; } /// /// 正常关闭程序。程序将在执行此方法后约 0.3s 退出。 /// /// 是否在还有下载任务未完成时发出警告。 /// 是否正在更新重启 public void EndProgram(bool sendWarning, bool isUpdating = false) { // 发出警告 if (sendWarning && ModNet.HasDownloadingTask()) { if (ModMain.MyMsgBox(Lang.Text("Main.Exit.HasDownloadingTask"), Lang.Text("Common.Dialog.Title"), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Cancel")) == 1) // 强行结束下载任务 ModBase.RunInNewThread(() => { ModBase.Log("[System] 正在强行停止任务"); foreach (var Task in ModLoader.loaderTaskbar.ToList()) Task.Abort(); }, "强行停止下载任务"); else return; } // 关闭联机大厅 // Await LobbyController.CloseAsync().ConfigureAwait(False) // 存储上次使用的档案编号 ModProfile.SaveProfile(); // 关闭 ModBase.RunInUiWait(() => { // 清理视频背景 VideoBack.Stop(); VideoBack.Source = null; VideoBack.Close(); IsHitTestVisible = false; if (RenderTransform is null) { var transformPos = new TranslateTransform(0d, 0d); var transformRotate = new RotateTransform(0d); var transformScale = new ScaleTransform(1d, 1d); transformScale.CenterX = Width / 2d; transformScale.CenterY = Height / 2d; RenderTransform = new TransformGroup { Children = new TransformCollection([transformRotate, transformPos, transformScale]) }; ModAnimation.AniStart(new[] { ModAnimation.AaOpacity(this, -Opacity, 140, 40, new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)), ModAnimation.AaDouble(i => { transformScale.ScaleX += (double)i; transformScale.ScaleY += (double)i; }, 0.88d - transformScale.ScaleX, 180), ModAnimation.AaDouble(i => transformPos.Y += (double)i, 20d - transformPos.Y, 180, 0, new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)), ModAnimation.AaDouble(i => transformRotate.Angle += (double)i, 0.6d - transformRotate.Angle, 180, 0, new ModAnimation.AniEaseInoutFluent(ModAnimation.AniEasePower.Weak)), ModAnimation.AaCode(() => { IsHitTestVisible = false; Visibility = Visibility.Collapsed; ShowInTaskbar = false; }, 210), ModAnimation.AaCode(() => EndProgramForce(force: false, isUpdating: isUpdating), 230) }, "Form Close"); } else { EndProgramForce(force: false, isUpdating: isUpdating); } ModBase.Log("[System] 收到关闭指令"); }); } private static bool isLogShown; public static void EndProgramForce(ModBase.ProcessReturnValues returnCode = ModBase.ProcessReturnValues.Success, bool force = true, bool isUpdating = false) { // On Error Resume Next // 关闭联机大厅 // Await LobbyController.CloseAsync().ConfigureAwait(False) ModBase.isProgramEnded = true; ModAnimation.AniControlEnabled += 1; if (UpdateManager.isUpdateWaitingRestart && !isUpdating) UpdateManager.UpdateRestart(false, false); if (returnCode == ModBase.ProcessReturnValues.Exception) { if (!isLogShown) { ModBase.FeedbackInfo(); ModBase.Log("请在 https://github.com/PCL-Community/PCL2-CE/issues 提交错误报告,以便于社区解决此问题!(这也有可能是原版 PCL 的问题)"); isLogShown = true; ModBase.ShellOnly(LogWrapper.CurrentLogger.CurrentLogFiles.Last()); } Thread.Sleep(500); // 防止 PCL 在记事本打开前就被掐掉 } ModBase.Log("[System] 程序已退出,返回值:" + ModBase.GetStringFromEnum(returnCode)); // If ReturnCode <> ProcessReturnValues.Success Then Environment.Exit(ReturnCode) // Process.GetCurrentProcess.Kill() Lifecycle.Shutdown((int)returnCode, force); } private void BtnTitleClose_Click(object sender, EventArgs e) { EndProgram(true); } // 移动 private void FormDragMove(object sender, MouseButtonEventArgs e) { // On Error Resume Next if (((Grid)sender).IsMouseDirectlyOver) DragMove(); } // 改变大小 /// /// 是否可以向注册表储存尺寸改变信息。以此避免初始化时误储存。 /// public bool isSizeSaveable; private void FormMain_SizeChanged(object? sender = null, EventArgs? e = null) { if (isSizeSaveable) { States.UI.WindowHeight = Height; States.UI.WindowWidth = Width; } if (PanBack is not null) { RectForm.Rect = new Rect(0d, 0d, PanBack.ActualWidth, PanBack.ActualHeight); var formWidth = PanBack.ActualWidth + 0.001d; var formHeight = PanBack.ActualHeight + 0.001d; PanForm.Width = formWidth; PanForm.Height = formHeight; PanMain.Width = formWidth; if (PanTitle is not null) PanMain.Height = Math.Max(0d, formHeight - PanTitle.ActualHeight); else PanMain.Height = formHeight; VideoBack.Width = formWidth; VideoBack.Height = formHeight; } if (WindowState == WindowState.Maximized) WindowState = WindowState.Normal; // 修复 #1938 } // 标题栏改变大小 private void PanTitle_SizeChanged(object sender, EventArgs e) { if (PanTitleMain.ColumnDefinitions[0].ActualWidth - 30 <= 0) PanTitleLeft.ColumnDefinitions[0].MaxWidth = 0; else PanTitleLeft.ColumnDefinitions[0].MaxWidth = PanTitleMain.ColumnDefinitions[0].ActualWidth - 30; } // 最小化 private void BtnTitleMin_Click(object sender, EventArgs e) { WindowState = WindowState.Minimized; } private void BtnTitleHelp_Click(object sender, EventArgs e) { ModBase.OpenWebsite("https://www.bilibili.com/video/BV1uT4y1P7CX"); } #endregion #region 窗体事件 public void AddResizer() { canResize = true; } public void RemoveResizer() { canResize = false; } // 按键事件 private void FormMain_KeyDown(object sender, KeyEventArgs e) { if (e.IsRepeat) return; // 调用弹窗:回车选择第一个,Esc 选择最后一个 if (PanMsg.Children.Count > 0) { if (e.Key == Key.Enter) { var msg = PanMsg.Children[0]; Action? enterAction = msg switch { MyMsgInput input => () => input.Btn1_Click(sender, null), MyMsgSelect select => () => select.Btn1_Click(sender, null), MyMsgText text => () => text.Btn1_Click(sender, null), MyMsgMarkdown markdown => () => markdown.Btn1_Click(sender, null), MyMsgLogin login => () => login.Btn1_Click(sender, null), _ => null }; enterAction?.Invoke(); return; } if (e.Key == Key.Escape) { var msg = PanMsg.Children[0]; Action? escapeAction = msg switch { MyMsgInput input => input.Btn2.Visibility == Visibility.Visible ? () => input.Btn2_Click(sender, null) : () => input.Btn1_Click(sender, null), MyMsgSelect select => select.Btn2.Visibility == Visibility.Visible ? () => select.Btn2_Click(sender, null) : () => select.Btn1_Click(sender, null), MyMsgText text => text.Btn3.Visibility == Visibility.Visible ? () => text.Btn3_Click(sender, null) : text.Btn2.Visibility == Visibility.Visible ? () => text.Btn2_Click(sender, null) : () => text.Btn1_Click(sender, null), MyMsgMarkdown markdown => markdown.Btn3.Visibility == Visibility.Visible ? () => markdown.Btn3_Click(sender, null) : markdown.Btn2.Visibility == Visibility.Visible ? () => markdown.Btn2_Click(sender, null) : () => markdown.Btn1_Click(sender, null), MyMsgLogin login => login.Btn3.Visibility == Visibility.Visible ? () => login.Btn3_Click(sender, null) : () => login.Btn1_Click(sender, null), _ => null }; escapeAction?.Invoke(); return; } } // 按 ESC 返回上一级 if (e.Key == Key.Escape) TriggerPageBack(); // 更改隐藏实例可见性 if (e.Key == Key.F11 && pageCurrent == PageType.InstanceSelect) { ModMain.frmSelectRight.showHidden = !ModMain.frmSelectRight.showHidden; ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected, ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\"); return; } // 更改功能隐藏可见性 if (e.Key == Key.F12) { PageSetupUI.HiddenForceShow = !PageSetupUI.HiddenForceShow; if (PageSetupUI.HiddenForceShow) HintService.Hint(Lang.Text("Main.HiddenFeature.Disabled"), HintType.Success); else HintService.Hint(Lang.Text("Main.HiddenFeature.Enabled"), HintType.Success); PageSetupUI.HiddenRefresh(); return; } // 按 F5 刷新页面 if (e.Key == Key.F5) { if (pageLeft is IRefreshable) ((IRefreshable)pageLeft).Refresh(); if (pageRight is IRefreshable) ((IRefreshable)pageRight).Refresh(); return; } // 调用启动游戏 if (e.Key == Key.Enter && pageCurrent == PageType.Launch) { if (ModMain.isAprilEnabled && !ModMain.isAprilGiveup) HintService.Hint(Lang.Text("Main.April.Nope")); else ModMain.frmLaunchLeft.LaunchButtonClick(); } // 修复按下 Alt 后误认为弹出系统菜单导致的冻结 if (e.SystemKey == Key.LeftAlt || e.SystemKey == Key.RightAlt) e.Handled = true; } private void FormMain_MouseDown(object sender, MouseButtonEventArgs e) { // 鼠标侧键返回上一级 if (ModMain.frmMain!.PanMsg.Children.Count > 0 || ModMain.WaitingMyMsgBox.Any()) return; // 弹窗中(#5513) if (e.ChangedButton == MouseButton.XButton1 || e.ChangedButton == MouseButton.XButton2) TriggerPageBack(); } private void TriggerPageBack() { if (pageCurrent == PageType.Download && PageCurrentSub == PageSubType.DownloadInstall && ModMain.frmDownloadInstall.isInSelectPage) ModMain.frmDownloadInstall.ExitSelectPage(); else if (pageCurrent == PageType.InstanceSetup && PageCurrentSub == PageSubType.VersionInstall && ModMain.frmInstanceInstall.isInSelectPage) ModMain.frmInstanceInstall.ExitSelectPage(); else PageBack(); } // 切回窗口 private void FormMain_Activated(object sender, EventArgs e) { try { if (Config.Download.Comp.ReadClipboard) ModComp.CompClipboard.GetClipboardResource(); if (pageCurrent == PageType.InstanceSetup && PageCurrentSub == PageSubType.VersionMod) { // Mod 管理自动刷新 ModMain.frmInstanceMod.ReloadCompFileList(); } else if (pageCurrent == PageType.InstanceSetup && PageCurrentSub == PageSubType.VersionResourcePack) { // 资源包管理自动刷新 if (ModMain.frmInstanceResourcePack is not null) ModMain.frmInstanceResourcePack.ReloadCompFileList(); } else if (pageCurrent == PageType.InstanceSetup && PageCurrentSub == PageSubType.VersionShader) { // 光影包管理自动刷新 if (ModMain.frmInstanceShader is not null) ModMain.frmInstanceShader.ReloadCompFileList(); } else if (pageCurrent == PageType.InstanceSetup && PageCurrentSub == PageSubType.VersionSchematic) { // 投影原理图管理自动刷新 if (ModMain.frmInstanceSchematic is not null) ModMain.frmInstanceSchematic.ReloadCompFileList(); } else if (pageCurrent == PageType.InstanceSelect) { // 实例选择自动刷新 ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected, ModLoader.LoaderFolderRunType.RunOnUpdated, 1, @"versions\"); } else if (ModMain.frmMain.pageRight is PageInstanceSavesDatapack && ModMain.frmInstanceSavesDatapack is not null) { // 数据包管理自动刷新 ModMain.frmInstanceSavesDatapack.ReloadDatapackFileList(); } } catch (Exception ex) { ModBase.Log( ex, "切回窗口时出错", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Main.Error.OperationFailed")); } } private IDataObject _HandleDrag_PrevData; private DragDropEffects _HandleDrag_PrevEffects; // 文件拖放 private void HandleDrag(object sender, DragEventArgs e) { try { if (e.Handled && e.Effects != DragDropEffects.None) return; // 缓存 e.Handled = true; if (ReferenceEquals(e.Data, _HandleDrag_PrevData)) { e.Effects = _HandleDrag_PrevEffects; return; } // 确定拖放效果 e.Effects = DragDropEffects.None; if (e.Data.GetDataPresent(DataFormats.Text)) { var str = (string)e.Data.GetData(DataFormats.Text); if (str.StartsWithF("authlib-injector:yggdrasil-server:")) e.Effects = DragDropEffects.Copy; else if (str.StartsWithF("file:///")) e.Effects = DragDropEffects.Copy; } else if (e.Data.GetDataPresent(DataFormats.FileDrop)) { var files = (string[])e.Data.GetData(DataFormats.FileDrop); if (files is not null && files.Length > 0) e.Effects = DragDropEffects.Link; } _HandleDrag_PrevData = e.Data; _HandleDrag_PrevEffects = e.Effects; ModBase.Log("[System] 设置拖放类型:" + ModBase.GetStringFromEnum(e.Effects)); } catch (Exception ex) { ModBase.Log( ex, "处理拖放时出错", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Main.Error.OperationFailed")); } } private void FrmMain_Drop(object sender, DragEventArgs e) { try { if (e.Data.GetDataPresent(DataFormats.Text)) { // 获取文本 try { var str = (string)e.Data.GetData(DataFormats.Text); ModBase.Log("[System] 接受文本拖拽:" + str); if (str.StartsWithF("authlib-injector:yggdrasil-server:")) { // Authlib 拖拽 e.Handled = true; e.Effects = DragDropEffects.Copy; var authlibServer = WebUtility.UrlDecode(str.Substring("authlib-injector:yggdrasil-server:".Length)); ModBase.Log("[System] Authlib 拖拽:" + authlibServer); if (!new HttpValidator().Validate(authlibServer).IsValid) { HintService.Hint(Lang.Text("Main.FileDrag.AuthlibInvalid", authlibServer), HintType.Error); return; } if (ModMain.MyMsgBox(Lang.Text("Main.FileDrag.CreateAuthlibProfile", authlibServer), Lang.Text("Main.FileDrag.CreateAuthlibProfileTitle"), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Cancel")) == 2) return; ModProfile.selectedProfile = null; ModBase.RunInUi(() => { PageLoginAuth.draggedAuthServer = authlibServer; ModMain.frmLaunchLeft.RefreshPage(true, ModLaunch.McLoginType.Auth); }); if (pageCurrent == PageType.InstanceSetup && PageCurrentSub == PageSubType.VersionSetup) // 正在服务器选项页,需要刷新设置项显示 ModMain.frmInstanceSetup.Reload(); } else if (str.StartsWithF("file:///")) { // 文件拖拽(例如从浏览器下载窗口拖入) var filePath = WebUtility.UrlDecode(str).Substring("file:///".Length).Replace("/", @"\"); e.Handled = true; e.Effects = DragDropEffects.Copy; FileDrag(new List { filePath }); } } catch (Exception ex) { ModBase.Log(ex, "无法接取文本拖拽事件", ModBase.LogLevel.Developer); } } else if (e.Data.GetDataPresent(DataFormats.FileDrop)) { // 获取文件并检查 var filePathRaw = e.Data.GetData(DataFormats.FileDrop); if (filePathRaw is null) // #2690 { HintService.Hint(Lang.Text("Main.FileDrag.ExtractFirst"), HintType.Error); return; } e.Handled = true; e.Effects = DragDropEffects.Link; FileDrag((IEnumerable)filePathRaw); } } catch (Exception ex) { ModBase.Log( ex, "接取拖拽事件失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Main.Error.OperationFailed")); } } private void FileDrag(IEnumerable filePathList) { ModBase.RunInNewThread(() => { var filePath = filePathList.First(); ModBase.Log("[System] 接受文件拖拽:" + filePath + (filePathList.Any() ? $" 等 {filePathList.Count()} 个文件" : ""), ModBase.LogLevel.Developer); // 基础检查 if (Directory.Exists(filePathList.First()) && !File.Exists(filePathList.First())) { HintService.Hint(Lang.Text("Main.FileDrag.FileOnly"), HintType.Error); return; } if (!File.Exists(filePathList.First())) { HintService.Hint(Lang.Text("Main.FileDrag.FileNotFound", filePathList.First()), HintType.Error); return; } // 多文件拖拽 if (filePathList.Count() > 1) { // 检查是否为同类型文件 var firstExtension = filePathList.First().AfterLast(".").ToLower(); var allSameType = filePathList.All(f => (f.AfterLast(".").ToLower() ?? "") == (firstExtension ?? "")); if (allSameType && new[] { "jar", "litemod", "disabled", "old", "litematic", "nbt", "schematic", "schem" }.Contains( firstExtension)) { } // 允许同类型的 Mod 文件或投影文件批量拖拽 else { HintService.Hint(Lang.Text("Main.FileDrag.SameTypeOnly"), HintType.Error); return; } } // 主页 var extension = filePath.AfterLast(".").ToLower(); if (extension == "xaml") { ModBase.Log("[System] 文件后缀为 XAML,作为主页加载"); if (File.Exists(ModBase.exePath + @"PCL\Custom.xaml")) if (ModMain.MyMsgBox(Lang.Text("Main.FileDrag.HomepageExists"), Lang.Text("Main.FileDrag.OverwriteTitle"), Lang.Text("Common.Action.Overwrite"), Lang.Text("Common.Action.Cancel")) == 2) return; ModBase.CopyFile(filePath, ModBase.exePath + @"PCL\Custom.xaml"); ModBase.RunInUi(() => { Config.Preference.Homepage.Type = 1; ModMain.frmLaunchRight.ForceRefresh(); HintService.Hint(Lang.Text("Main.FileDrag.HomepageLoaded"), HintType.Success); }); return; } // 安装 Mod if (PageInstanceCompResource.InstallMods(filePathList)) return; // 安装投影文件 if (new[] { "litematic", "nbt", "schematic", "schem" }.Contains(extension)) { ModBase.Log($"[System] 文件为 {extension} 格式,尝试作为原理图安装"); // 获取当前文件夹路径(如果在资源管理页面) string targetFolderPath = null; if (pageCurrent == PageType.InstanceSetup && PageCurrentSub == PageSubType.VersionSchematic && ModMain.frmInstanceSchematic is not null && ModMain.frmInstanceSchematic is PageInstanceCompResource) targetFolderPath = ModMain.frmInstanceSchematic.CurrentFolderPath; PageInstanceCompResource.InstallCompFiles(filePathList, ModComp.CompType.Schematic, targetFolderPath); return; } // 处理资源安装 if (pageCurrent == PageType.InstanceSetup && new[] { "zip" }.Any(i => (i ?? "") == (extension ?? ""))) switch (PageCurrentSub) { case PageSubType.VersionWorld: { var destFolder = PageInstanceLeft.McInstance.PathIndie + @"saves\" + ModBase.GetFileNameWithoutExtentionFromPath(filePath); var destLevelDat = Path.Combine(destFolder, "level.dat"); if (Directory.Exists(destFolder)) { HintService.Hint(Lang.Text("Main.FileDrag.SameFolderExists", destFolder), HintType.Error); return; } var extractFolder = Path.Combine(ModBase.pathTemp, "Cache", "WorldImport", ModBase.GetUuid().ToString()); try { ModBase.ExtractFile(filePath, extractFolder); var saveRoot = SaveImportHelper.GetSaveRootDirectory(extractFolder); if (saveRoot is null) { HintService.Hint(Lang.Text("Main.FileDrag.SaveNotFound"), HintType.Error); return; } ModBase.CopyDirectory(saveRoot, destFolder); if (!File.Exists(destLevelDat)) { if (Directory.Exists(destFolder)) ModBase.DeleteDirectory(destFolder, true); HintService.Hint(Lang.Text("Main.FileDrag.SaveInvalid"), HintType.Error); return; } } catch (Exception ex) { if (Directory.Exists(destFolder)) ModBase.DeleteDirectory(destFolder, true); ModBase.Log( ex, Lang.Text("Main.FileDrag.SaveImportFailed"), ModBase.LogLevel.Hint, userSummary: Lang.Text("Main.FileDrag.SaveImportFailed")); return; } finally { if (Directory.Exists(extractFolder)) ModBase.DeleteDirectory(extractFolder, true); } HintService.Hint(Lang.Text("Main.FileDrag.Imported", ModBase.GetFileNameWithoutExtentionFromPath(filePath)), HintType.Success); if (ModMain.frmInstanceSaves is not null) ModBase.RunInUi(() => ModMain.frmInstanceSaves.Reload()); return; } case PageSubType.VersionResourcePack: { var destFile = PageInstanceLeft.McInstance.PathIndie + @"resourcepacks\" + ModBase.GetFileNameFromPath(filePath); if (File.Exists(destFile)) { HintService.Hint(Lang.Text("Main.FileDrag.SameFileExists", destFile), HintType.Error); return; } ModBase.CopyFile(filePath, destFile); HintService.Hint(Lang.Text("Main.FileDrag.Imported", ModBase.GetFileNameFromPath(filePath)), HintType.Success); if (ModMain.frmInstanceResourcePack is not null) ModBase.RunInUi(() => ModMain.frmInstanceResourcePack.ReloadCompFileList()); return; } case PageSubType.VersionShader: { var destFile = PageInstanceLeft.McInstance.PathIndie + @"shaderpacks\" + ModBase.GetFileNameFromPath(filePath); if (File.Exists(destFile)) { HintService.Hint(Lang.Text("Main.FileDrag.SameFileExists", destFile), HintType.Error); return; } ModBase.CopyFile(filePath, destFile); HintService.Hint(Lang.Text("Main.FileDrag.Imported", ModBase.GetFileNameFromPath(filePath)), HintType.Success); if (ModMain.frmInstanceShader is not null) ModBase.RunInUi(() => ModMain.frmInstanceShader.ReloadCompFileList()); return; } } // 处理投影文件 if (pageCurrent == PageType.InstanceSetup && new[] { "litematic", "nbt", "schematic", "schem" }.Contains(extension) && PageCurrentSub == PageSubType.VersionSchematic) { var destFile = PageInstanceLeft.McInstance.PathIndie + @"schematics\" + ModBase.GetFileNameFromPath(filePath); if (File.Exists(destFile)) { HintService.Hint(Lang.Text("Main.FileDrag.SameFileExists", destFile), HintType.Error); return; } Directory.CreateDirectory(PageInstanceLeft.McInstance.PathIndie + @"schematics\"); ModBase.CopyFile(filePath, destFile); HintService.Hint(Lang.Text("Main.FileDrag.Imported", ModBase.GetFileNameFromPath(filePath)), HintType.Success); if (ModMain.frmInstanceSchematic is not null) ModBase.RunInUi(() => ModMain.frmInstanceSchematic.ReloadCompFileList()); return; } // 安装整合包 if (new[] { "zip", "rar", "mrpack" }.Any(t => (t ?? "") == (extension ?? ""))) // 部分压缩包是 zip 格式但后缀为 rar,总之试一试 { ModBase.Log("[System] 文件为压缩包,尝试作为整合包安装"); try { ModModpack.ModpackInstall(filePath); return; } catch (ModBase.CancelledException ex) { return; // 用户主动取消 } catch (Exception ex) { // 安装失败,继续往后尝试 } } // 错误报告分析 do { try { ModBase.Log("[System] 尝试进行错误报告分析"); var analyzer = new CrashAnalyzer(ModBase.GetUuid()); analyzer.Import(filePath); if (!analyzer.Prepare()) break; analyzer.Analyze(); analyzer.Output(true, new List()); return; } catch (Exception ex) { ModBase.Log( ex, "自主错误报告分析失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Main.Error.OperationFailed")); } } while (false); // 未知操作 HintService.Hint(Lang.Text("Main.FileDrag.UnknownOperation")); }, "文件拖拽"); } // 接受到 Windows 窗体事件 public bool isSystemTimeChanged; private nint WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled) { if (msg == 30) { var nowDate = DateTime.Now; if (nowDate.Date == ModBase.applicationOpenTime.Date) { ModBase.Log("[System] 系统时间微调为:" + nowDate.ToLongDateString() + " " + nowDate.ToLongTimeString()); isSystemTimeChanged = false; } else { ModBase.Log("[System] 系统时间修改为:" + nowDate.ToLongDateString() + " " + nowDate.ToLongTimeString()); isSystemTimeChanged = true; } } else if (msg == 400 * 16 + 2) { ModBase.Log("[System] 收到置顶信息:" + hwnd.ToInt64()); if (!isWindowLoadFinished) { ModBase.Log("[System] 窗口尚未加载完成,忽略置顶请求"); return nint.Zero; } ShowWindowToTop(); handled = true; } else if (msg == 26) // WM_SETTINGCHANGE { if (Marshal.PtrToStringAuto(lParam) == "ImmersiveColorSet") { ModBase.Log($"[System] 系统主题更改,深色模式:{SystemTheme.IsSystemInDarkMode()}"); if (Config.Preference.Theme.ColorMode == ColorMode.System && (ThemeManager.IsDarkMode != SystemTheme.IsSystemInDarkMode())) ThemeService.RefreshColorMode(); } } return nint.Zero; } // 窗口隐藏与置顶 public bool Hidden { get => field; set { if (field == value) return; field = value; if (value) { // 隐藏 Left -= 10000d; ShowInTaskbar = false; Visibility = Visibility.Hidden; ModBase.Log("[System] 窗口已隐藏,位置:(" + Left + "," + Top + ")"); } else { // 取消隐藏 if (Left < -2000) Left += 10000d; ShowWindowToTop(); } } } // 解决龙猫的非通用实现史山 protected override void OnActivated(EventArgs e) { base.OnActivated(e); if (Hidden) Hidden = false; } /// /// 把当前窗口拖到最前面。 /// public void ShowWindowToTop() { ModBase.RunInUi(() => { // 这一坨乱七八糟的,别改,改了指不定就炸了,自己电脑还复现不出来 Visibility = Visibility.Visible; ShowInTaskbar = true; WindowState = WindowState.Normal; Hidden = false; Topmost = true; // 偶尔 SetForegroundWindow 失效 Topmost = false; ModMain.SetForegroundWindow(ModBase.frmHandle); Focus(); ModBase.Log($"[System] 窗口已置顶,位置:({Left}, {Top}), {Width} x {Height}"); }); } // 背景视频循环播放 private void VideoEnded(object sender, RoutedEventArgs e) { VideoBack.Position = TimeSpan.Zero; VideoBack.Play(); } // 最小化时暂停背景视频 private void WindowStateChanged(object sender, EventArgs e) { switch (WindowState) { case WindowState.Minimized: { ModVideoBack.isMinimized = true; ModVideoBack.VideoPause(); break; } case WindowState.Normal: { ModVideoBack.isMinimized = false; ModVideoBack.VideoPlay(); break; } } } #endregion #region 切换页面 // 页面种类与属性 // 注意,这一枚举在 “切换页面” EventType 中调用,应视作公开 API 的一部分 /// /// 页面种类。 /// public enum PageType { /// /// 启动。 /// Launch = 0, /// /// 下载。 /// Download = 1, /// /// 联机。 /// Tools = 3, /// /// 设置。 /// Setup = 2, /// /// 实例选择。这是一个副页面。 /// InstanceSelect = 5, /// /// 任务管理。这是一个副页面。 /// TaskManager = 6, /// /// 实例设置。这是一个副页面。 /// InstanceSetup = 7, /// /// 资源工程详情。这是一个副页面。 /// CompDetail = 8, /// /// 游戏实时日志。这是一个副页面。 /// GameLog = 10, /// /// 存档详细管理,这是一个副页面。 /// VersionSaves = 12, } /// /// 次要页面种类。其数值必须与 StackPanel 中的下标一致。 /// public enum PageSubType { Default = 0, DownloadInstall = 1, DownloadMod = 2, DownloadPack = 3, DownloadDataPack = 4, DownloadResourcePack = 5, DownloadShader = 6, DownloadWorld = 7, DownloadCompFavorites = 8, DownloadClient = 9, DownloadOptiFine = 10, DownloadForge = 11, DownloadNeoForge = 12, DownloadCleanroom = 13, DownloadFabric = 14, DownloadLiteLoader = 16, DownloadLabyMod = 17, DownloadLegacyFabric = 18, SetupLaunch = 0, SetupUI = 1, SetupGameManage = 2, SetupLink = 3, SetupAbout = 4, SetupLog = 5, SetupFeedback = 6, SetupGameLink = 7, SetupUpdate = 8, SetupJava = 9, SetupLauncherMisc = 10, SetupLauncherLanguage = 11, ToolsGameLink = 1, ToolsTest = 3, VersionOverall = 0, VersionSetup = 1, VersionExport = 2, VersionWorld = 3, VersionScreenshot = 4, VersionMod = 5, VersionModDisabled = 6, VersionResourcePack = 7, VersionShader = 8, VersionSchematic = 9, VersionInstall = 10, VersionServer = 11, VersionSavesInfo = 0, VersionSavesDatapack = 1 } /// /// 获取次级页面的名称。若并非次级页面则返回空字符串,故可以以此判断是否为次级页面。 /// private string PageNameGet(PageStackData stack) { switch (stack.page) { case PageType.InstanceSelect: { return Lang.Text("Main.Title.InstanceSelect"); } case PageType.TaskManager: { return Lang.Text("Main.Title.TaskManager"); } case PageType.GameLog: { return Lang.Text("Main.Title.GameLog"); } case PageType.InstanceSetup: { return Lang.Text("Main.Title.InstanceSetup", PageInstanceLeft.McInstance is null ? Lang.Text("Common.State.Unknown") : PageInstanceLeft.McInstance.Name); } case PageType.CompDetail: { return Lang.Text("Main.Title.ResourceDownload", stack.additional.Value.CompProject.TranslatedName); } case PageType.VersionSaves: { return Lang.Text("Main.Title.SaveManagement", ModBase.GetFolderNameFromPath(stack.additional.Value.SavePath)); } default: { return ""; } } } /// /// 刷新次级页面的名称。 /// public void PageNameRefresh(PageStackData type) { LabTitleInner.Text = PageNameGet(type); } /// /// 刷新次级页面的名称。 /// public void PageNameRefresh() { PageNameRefresh(pageCurrent); } // 页面状态存储 /// /// 当前的主页面。 /// public PageStackData pageCurrent = PageType.Launch; /// /// 上一个主页面。 /// public PageStackData pageLast = PageType.Launch; /// /// 当前的子页面。 /// public PageSubType PageCurrentSub { get { switch (pageCurrent.page) { case PageType.Download: { if (ModMain.frmDownloadLeft is null) ModMain.frmDownloadLeft = new PageDownloadLeft(); return ModMain.frmDownloadLeft.pageID; } case PageType.Setup: { if (ModMain.frmSetupLeft is null) ModMain.frmSetupLeft = new PageSetupLeft(); return ModMain.frmSetupLeft.pageID; } case PageType.InstanceSetup: { if (ModMain.frmInstanceLeft is null) ModMain.frmInstanceLeft = new PageInstanceLeft(); return ModMain.frmInstanceLeft.pageID; } default: { return 0; // 没有子页面 } } } } /// /// 上层页面的编号堆栈,用于返回。 /// public List pageStack = new(); public class PageStackData { /// /// /// CompDetail: (CompProject, ExpandedTitles, TargetVersion, TargetLoader, ResourceType) /// VersionSaves: SavePath /// /// public ( ModComp.CompProject CompProject, List ExpandedTitles, string TargetVersion, ModComp.CompLoaderType TargetLoader, ModComp.CompType ResourceType, string SavePath )? additional; public PageType page; public override bool Equals(object other) { if (other is null) return false; if (other is PageStackData) { var pageOther = (PageStackData)other; if (page != pageOther.page) return false; if (additional is null) return pageOther.additional is null; return pageOther.additional is not null && additional.Equals(pageOther.additional); } if (other is int o) { if ((int)page == o) return false; return additional is null; } return false; } public static bool operator ==(PageStackData left, PageStackData right) { return EqualityComparer.Default.Equals(left, right); } public static bool operator !=(PageStackData left, PageStackData right) { return !(left == right); } public static implicit operator PageStackData(PageType value) { return new PageStackData { page = value }; } public static implicit operator PageType(PageStackData value) { return value.page; } } public MyPageLeft pageLeft; public MyPageRight pageRight; // 引发实际页面切换的入口 private bool isChangingPage; /// /// 切换页面,并引起对应选择 UI 的改变。 /// public void PageChange(PageStackData stack, PageSubType subType = PageSubType.Default) { if (string.IsNullOrEmpty(PageNameGet(stack))) { // 切换到主页面 PageChangeExit(); isChangingPage = true; // 防止下面的勾选直接触发了 PageChangeActual ((MyRadioButton)PanTitleSelect.Children[(int)stack.page]).SetChecked(true, true, string.IsNullOrEmpty(PageNameGet(pageCurrent))); isChangingPage = false; switch (stack.page) { case PageType.Download: { if (ModMain.frmDownloadLeft is null) ModMain.frmDownloadLeft = new PageDownloadLeft(); foreach (var item in ModMain.frmDownloadLeft.PanItem.Children) if (item is MyListItem listItem && ModBase.Val(listItem.Tag) == (double)subType) { listItem.SetChecked(true, true, stack == pageCurrent); break; } break; } case PageType.Setup: { if (ModMain.frmSetupLeft is null) ModMain.frmSetupLeft = new PageSetupLeft(); foreach (var item in ModMain.frmSetupLeft.PanItem.Children) if (item is MyListItem listItem && ModBase.Val(listItem.Tag) == (double)subType) { listItem.SetChecked(true, true, stack == pageCurrent); break; } break; } } PageChangeActual(stack, subType); } else { // 切换到次页面 switch (stack.page) { case PageType.InstanceSetup: { if (ModMain.frmInstanceLeft is null) ModMain.frmInstanceLeft = new PageInstanceLeft(); foreach (var item in ModMain.frmInstanceLeft.PanItem.Children) if (item is MyListItem listItem && ModBase.Val(listItem.Tag) == (double)subType) { listItem.SetChecked(true, true, stack == pageCurrent); break; } break; } case PageType.VersionSaves: { if (ModMain.frmInstanceSavesLeft is null) ModMain.frmInstanceSavesLeft = new PageInstanceSavesLeft(); foreach (var item in ModMain.frmInstanceSavesLeft.PanItem.Children) if (item is MyListItem listItem && ModBase.Val(listItem.Tag) == (double)subType) { listItem.SetChecked(true, true, stack == pageCurrent); break; } break; } } PageChangeActual(stack, subType); } } /// /// 通过点击导航栏改变页面。 /// private void BtnTitleSelect_Click(MyRadioButton sender, bool raiseByMouse) { if (isChangingPage) return; var pageType = (PageType)int.Parse(sender.Tag.ToString()); PageChangeActual(pageType, PageSubType.Default); } private void BtnTitleInner_Click(object sender, EventArgs e) { PageBack(); } /// /// 通过点击返回按钮或手动触发返回来改变页面。 /// public void PageBack() { if (pageStack.Any()) PageChangeActual(pageStack[0], PageSubType.Default); else PageChange(PageType.Launch); } // 实际处理页面切换 /// /// 切换现有页面的实际方法。 /// private void PageChangeActual(PageStackData stack, PageSubType subType) { if (pageCurrent == stack && (PageCurrentSub == subType || (int)subType == -1)) return; ModAnimation.AniControlEnabled += 1; try { #region 子页面处理 var pageName = PageNameGet(stack); if (string.IsNullOrEmpty(pageName)) { // 即将切换到一个顶级页面 PageChangeExit(); } // 即将切换到一个子页面 else if (pageStack.Any()) { // 子页面 → 另一个子页面,更新 ModAnimation.AniStart( new[] { ModAnimation.AaOpacity(LabTitleInner, -LabTitleInner.Opacity, 130), ModAnimation.AaCode(() => LabTitleInner.Text = pageName, after: true), ModAnimation.AaOpacity(LabTitleInner, 1d, 150, 30) }, "FrmMain Titlebar SubLayer"); if (pageStack.Contains(stack)) // 返回到更上层的子页面 while (pageStack.Contains(stack)) pageStack.RemoveAt(0); else // 进入更深层的子页面 pageStack.Insert(0, pageCurrent); } else { // 主页面 → 子页面,进入 PanTitleInner.Visibility = Visibility.Visible; PanTitleMain.IsHitTestVisible = false; PanTitleInner.IsHitTestVisible = true; PageNameRefresh(stack); ModAnimation.AniStart( new[] { ModAnimation.AaOpacity(PanTitleMain, -PanTitleMain.Opacity, 150), ModAnimation.AaX(PanTitleMain, 12d - PanTitleMain.Margin.Left, 150, ease: new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)), ModAnimation.AaOpacity(PanTitleInner, 1d - PanTitleInner.Opacity, 150, 200), ModAnimation.AaX(PanTitleInner, -PanTitleInner.Margin.Left, 350, 200, new ModAnimation.AniEaseOutBack()), ModAnimation.AaCode(() => PanTitleMain.Visibility = Visibility.Collapsed, after: true) }, "FrmMain Titlebar FirstLayer"); pageStack.Insert(0, pageCurrent); } #endregion #region 实际更改页面框架 UI pageLast = pageCurrent; pageCurrent = stack; switch (stack.page) { case PageType.Launch: // 启动 { PageChangeAnim(ModMain.frmLaunchLeft, ModMain.frmLaunchRight); break; } case PageType.Download: // 下载 { ModMain.frmDownloadLeft ??= new PageDownloadLeft(); if (subType != PageSubType.Default) ModMain.frmDownloadLeft.pageID = subType; else subType = ModMain.frmDownloadLeft.pageID; // PageGet 方法会在未设置 SubType 时指定默认值,并建立相关页面的实例 PageChangeAnim(ModMain.frmDownloadLeft, (FrameworkElement)ModMain.frmDownloadLeft.PageGet(subType)); break; } case PageType.Tools: // 联机 { ModMain.frmToolsLeft ??= new PageToolsLeft(); subType = ModMain.frmToolsLeft.pageID; PageChangeAnim(ModMain.frmToolsLeft, (FrameworkElement)ModMain.frmToolsLeft.PageGet(subType)); break; } case PageType.Setup: // 设置 { ModMain.frmSetupLeft ??= new PageSetupLeft(); subType = ModMain.frmSetupLeft.pageID; PageChangeAnim(ModMain.frmSetupLeft, (FrameworkElement)ModMain.frmSetupLeft.PageGet(subType)); break; } case PageType.GameLog: // 实时日志 { if (ModMain.frmLogLeft is null) ModMain.frmLogLeft = new PageLogLeft(); if (ModMain.frmLogLeft is null) ModMain.frmLogRight = new PageLogRight(); PageChangeAnim(ModMain.frmLogLeft, ModMain.frmLogRight); break; } case PageType.InstanceSelect: // 实例选择 { if (ModMain.frmSelectLeft is null) ModMain.frmSelectLeft = new PageSelectLeft(); if (ModMain.frmSelectRight is null) ModMain.frmSelectRight = new PageSelectRight(); PageChangeAnim(ModMain.frmSelectLeft, ModMain.frmSelectRight); break; } case PageType.TaskManager: // 任务管理 { if (ModMain.frmSpeedLeft is null) ModMain.frmSpeedLeft = new PageSpeedLeft(); if (ModMain.frmSpeedRight is null) ModMain.frmSpeedRight = new PageSpeedRight(); PageChangeAnim(ModMain.frmSpeedLeft, ModMain.frmSpeedRight); break; } case PageType.InstanceSetup: // 实例设置 { ModMain.frmInstanceLeft ??= new PageInstanceLeft(); subType = ModMain.frmInstanceLeft.pageID; PageChangeAnim(ModMain.frmInstanceLeft, (FrameworkElement)ModMain.frmInstanceLeft.PageGet(subType)); break; } case PageType.CompDetail: // Mod 信息 { if (ModMain.frmDownloadCompDetail is null) ModMain.frmDownloadCompDetail = new PageDownloadCompDetail(); PageChangeAnim(new MyPageLeft(), ModMain.frmDownloadCompDetail); break; } case PageType.VersionSaves: // 存档管理 { if (ModMain.frmInstanceSavesLeft is null) ModMain.frmInstanceSavesLeft = new PageInstanceSavesLeft(); PageInstanceSavesLeft.currentSave = stack.additional.Value.SavePath; subType = ModMain.frmInstanceSavesLeft.pageID; PageChangeAnim(ModMain.frmInstanceSavesLeft, (FrameworkElement)ModMain.frmInstanceSavesLeft.PageGet(subType)); break; } } #endregion #region 设置为最新状态 BtnExtraDownload.ShowRefresh(); BtnExtraApril.ShowRefresh(); #endregion ModBase.Log("[Control] 切换主要页面:" + ModBase.GetStringFromEnum(stack) + ", " + (int)subType); } catch (Exception ex) { ModBase.Log( ex, "切换主要页面失败(ID " + (int)pageCurrent.page + ")", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Main.Error.OperationFailed")); } finally { ModAnimation.AniControlEnabled -= 1; } } private void PageChangeAnim(FrameworkElement targetLeft, FrameworkElement targetRight) { ModAnimation.AniStop("FrmMain LeftChange"); ModAnimation.AniStop("PageLeft PageChange"); // 停止左边栏变更导致的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter ModAnimation.AniControlEnabled += 1; // 清除新页面关联性 if (targetLeft.Parent is not null) targetLeft.SetValue(ContentPresenter.ContentProperty, null); if (targetRight is not null && targetRight.Parent is not null) targetRight.SetValue(ContentPresenter.ContentProperty, null); pageLeft = (MyPageLeft)targetLeft; pageRight = (MyPageRight)targetRight; // 触发页面通用动画 ((MyPageLeft)PanMainLeft.Child).TriggerHideAnimation(); ((MyPageRight)PanMainRight.Child).PageOnExit(); ModAnimation.AniControlEnabled -= 1; // 执行动画 ModAnimation.AniStart(new[] { ModAnimation.AaCode(() => { ModAnimation.AniControlEnabled += 1; // 把新页面添加进容器 PanMainLeft.Child = pageLeft; pageLeft.Opacity = 0d; PanMainLeft.Background = null; ModAnimation.AniControlEnabled -= 1; ModBase.RunInUi(() => PanMainLeft_Resize(PanMainLeft.ActualWidth), true); }, 110), ModAnimation.AaCode(() => { // 延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理 pageLeft.Opacity = 1d; pageLeft.TriggerShowAnimation(); }, 30, true) }, "FrmMain PageChangeLeft"); ModAnimation.AniStart(new[] { ModAnimation.AaCode(() => { ModAnimation.AniControlEnabled += 1; ((MyPageRight)PanMainRight.Child).PageOnForceExit(); // 把新页面添加进容器 PanMainRight.Child = pageRight; pageRight.Opacity = 0d; PanMainRight.Background = null; ModAnimation.AniControlEnabled -= 1; ModBase.RunInUi(() => BtnExtraBack.ShowRefresh(), true); }, 110), ModAnimation.AaCode(() => { // 延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理 pageRight.Opacity = 1d; pageRight.PageOnEnter(); }, 30, true) }, "FrmMain PageChangeRight"); } /// /// 退出子界面。 /// private void PageChangeExit() { if (pageStack.Any()) { // 子页面 → 主页面,退出 PanTitleMain.Visibility = Visibility.Visible; PanTitleMain.IsHitTestVisible = true; PanTitleInner.IsHitTestVisible = false; ModAnimation.AniStart( new[] { ModAnimation.AaOpacity(PanTitleInner, -PanTitleInner.Opacity, 150), ModAnimation.AaX(PanTitleInner, -18 - PanTitleInner.Margin.Left, 150, ease: new ModAnimation.AniEaseInFluent()), ModAnimation.AaOpacity(PanTitleMain, 1d - PanTitleMain.Opacity, 150, 200), ModAnimation.AaX(PanTitleMain, -PanTitleMain.Margin.Left, 350, 200, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), ModAnimation.AaCode(() => PanTitleInner.Visibility = Visibility.Collapsed, after: true) }, "FrmMain Titlebar FirstLayer"); pageStack.Clear(); } // 主页面 → 主页面,无事发生 } // 左边栏改变 private void PanMainLeft_SizeChanged(object sender, SizeChangedEventArgs e) { if (!e.WidthChanged) return; PanMainLeft_Resize(e.NewSize.Width); } private void PanMainLeft_Resize(double newWidth) { var delta = newWidth - RectLeftBackground.Width; if (Math.Abs(delta) > 0.1d && ModAnimation.AniControlEnabled == 0) { if (PanMain.Opacity < 0.1d) PanMainLeft.IsHitTestVisible = false; // 避免左边栏指向背景未能完美覆盖左边栏 if (newWidth > 0d) // 宽度足够,显示 ModAnimation.AniStart( new[] { ModAnimation.AaWidth(RectLeftBackground, newWidth - RectLeftBackground.Width, 180, ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.ExtraStrong)), ModAnimation.AaOpacity(RectLeftShadow, 1d - RectLeftShadow.Opacity, 180), ModAnimation.AaCode(() => PanMainLeft.IsHitTestVisible = true, 150) }, "FrmMain LeftChange", true); else // 宽度不足,隐藏 ModAnimation.AniStart( new[] { ModAnimation.AaWidth(RectLeftBackground, -RectLeftBackground.Width, 180, ease: new ModAnimation.AniEaseOutFluent()), ModAnimation.AaOpacity(RectLeftShadow, -RectLeftShadow.Opacity, 180), ModAnimation.AaCode(() => PanMainLeft.IsHitTestVisible = true, 150) }, "FrmMain LeftChange", true); } else { RectLeftBackground.Width = newWidth; PanMainLeft.IsHitTestVisible = true; ModAnimation.AniStop("FrmMain LeftChange"); } } #endregion #region 控件拖动 // 在时钟中调用,使得即使鼠标在窗口外松开,也可以释放控件 public void DragTick() { if (ModMain.dragControl is null) return; if (!(Mouse.LeftButton == MouseButtonState.Pressed)) DragStop(); } // 在鼠标移动时调用,以改变 Slider 位置 public void DragDoing() { if (ModMain.dragControl is null) return; if (Mouse.LeftButton == MouseButtonState.Pressed) { ModMain.dragControl.DragDoing(); } else DragStop(); } private void PanBack_MouseMove(object sender, EventArgs e) { DragDoing(); } public void DragStop() { // 存在其他线程调用的可能性,因此需要确保在 UI 线程运行 ModBase.RunInUi(() => { if (ModMain.dragControl is null) return; var control = ModMain.dragControl; ModMain.dragControl = null; control.DragStop(); // 控件会在该事件中判断 DragControl,所以得放在后面 }); } #endregion #region 附加按钮 // 更新重启 private void BtnExtraUpdateRestart_Click(object sender, MouseButtonEventArgs e) { UpdateManager.UpdateRestart(true); } private bool BtnExtraUpdateRestart_ShowCheck() { return UpdateManager.isUpdateWaitingRestart; } // 音乐 private void BtnExtraMusic_Click(object sender, MouseButtonEventArgs e) { ModMusic.MusicControlPause(); } private void BtnExtraMusic_RightClick(object sender, MouseButtonEventArgs e) { ModMusic.MusicControlNext(); } // 任务管理 private void BtnExtraDownload_Click(object sender, MouseButtonEventArgs e) { PageChange(PageType.TaskManager); } private bool BtnExtraDownload_ShowCheck() { return ModNet.HasDownloadingTask() && !(pageCurrent == PageType.TaskManager); } // 投降 public void AprilGiveup() { if (ModMain.isAprilEnabled && !ModMain.isAprilGiveup) { HintService.Hint("=D", HintType.Success); ModMain.isAprilGiveup = true; ModMain.frmLaunchLeft.AprilScaleTrans.ScaleX = 1d; ModMain.frmLaunchLeft.AprilScaleTrans.ScaleY = 1d; BtnExtraApril.ShowRefresh(); } } private void BtnExtraApril_Click(object sender, MouseButtonEventArgs e) { AprilGiveup(); } public bool BtnExtraApril_ShowCheck() { return ModMain.isAprilEnabled && !ModMain.isAprilGiveup && pageCurrent == PageType.Launch; } // 关闭 Minecraft private void BtnExtraShutdown_Click(object sender, MouseButtonEventArgs e) { try { if (ModLaunch.mcLaunchLoaderReal is not null) ModLaunch.mcLaunchLoaderReal.Abort(); foreach (var Watcher in ModWatcher.mcWatcherList) Watcher.Kill(); HintService.Hint(Lang.Text("Main.ShutdownMinecraft.Success"), HintType.Success); } catch (Exception ex) { ModBase.Log( ex, "强制关闭所有 Minecraft 失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Main.Error.OperationFailed")); } } public bool BtnExtraShutdown_ShowCheck() { return ModWatcher.hasRunningMinecraft; } // 游戏日志 private void BtnExtraLog_Click(object sender, MouseButtonEventArgs e) { PageChange(PageType.GameLog); } public bool BtnExtraLog_ShowCheck() { if (ModMain.frmLogLeft is null || ModMain.frmLogRight is null || pageCurrent == PageType.GameLog) return false; return ModMain.frmLogLeft.shownLogs.Count > 0; } /// /// 返回顶部。 /// public void BackToTop() { var realScroll = BtnExtraBack_GetRealChild(); if (realScroll is not null) realScroll.PerformVerticalOffsetDelta(-realScroll.VerticalOffset); else ModBase.Log( "[UI] 无法返回顶部,未找到合适的 RealScroll", ModBase.LogLevel.Hint, userSummary: Lang.Text("Main.Error.ScrollToTopFailed")); } private void BtnExtraBack_Click(object sender, MouseButtonEventArgs e) { BackToTop(); } private bool BtnExtraBack_ShowCheck() { var realScroll = BtnExtraBack_GetRealChild(); return realScroll is not null && realScroll.Visibility == Visibility.Visible && realScroll.VerticalOffset > Height + (BtnExtraBack.Show ? 0 : 700); } private MyScrollViewer? BtnExtraBack_GetRealChild() { if (PanMainRight.Child is null || !(PanMainRight.Child is MyPageRight)) return null; return ((MyPageRight)PanMainRight.Child).PanScroll; } #endregion }