Files
xyou f70b061d1a
CI / Go Backend (push) Canceled after 0s
feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2026-08-08 14:07:56 +08:00

2246 lines
84 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
/// <summary>
/// 正常关闭程序。程序将在执行此方法后约 0.3s 退出。
/// </summary>
/// <param name="sendWarning">是否在还有下载任务未完成时发出警告。</param>
/// <param name="isUpdating">是否正在更新重启</param>
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();
}
// 改变大小
/// <summary>
/// 是否可以向注册表储存尺寸改变信息。以此避免初始化时误储存。
/// </summary>
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<string> { 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<string>)filePathRaw);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"接取拖拽事件失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Main.Error.OperationFailed"));
}
}
private void FileDrag(IEnumerable<string> 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<string>());
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;
}
/// <summary>
/// 把当前窗口拖到最前面。
/// </summary>
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 的一部分
/// <summary>
/// 页面种类。
/// </summary>
public enum PageType
{
/// <summary>
/// 启动。
/// </summary>
Launch = 0,
/// <summary>
/// 下载。
/// </summary>
Download = 1,
/// <summary>
/// 联机。
/// </summary>
Tools = 3,
/// <summary>
/// 设置。
/// </summary>
Setup = 2,
/// <summary>
/// 实例选择。这是一个副页面。
/// </summary>
InstanceSelect = 5,
/// <summary>
/// 任务管理。这是一个副页面。
/// </summary>
TaskManager = 6,
/// <summary>
/// 实例设置。这是一个副页面。
/// </summary>
InstanceSetup = 7,
/// <summary>
/// 资源工程详情。这是一个副页面。
/// </summary>
CompDetail = 8,
/// <summary>
/// 游戏实时日志。这是一个副页面。
/// </summary>
GameLog = 10,
/// <summary>
/// 存档详细管理,这是一个副页面。
/// </summary>
VersionSaves = 12,
}
/// <summary>
/// 次要页面种类。其数值必须与 StackPanel 中的下标一致。
/// </summary>
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
}
/// <summary>
/// 获取次级页面的名称。若并非次级页面则返回空字符串,故可以以此判断是否为次级页面。
/// </summary>
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 "";
}
}
}
/// <summary>
/// 刷新次级页面的名称。
/// </summary>
public void PageNameRefresh(PageStackData type)
{
LabTitleInner.Text = PageNameGet(type);
}
/// <summary>
/// 刷新次级页面的名称。
/// </summary>
public void PageNameRefresh()
{
PageNameRefresh(pageCurrent);
}
// 页面状态存储
/// <summary>
/// 当前的主页面。
/// </summary>
public PageStackData pageCurrent = PageType.Launch;
/// <summary>
/// 上一个主页面。
/// </summary>
public PageStackData pageLast = PageType.Launch;
/// <summary>
/// 当前的子页面。
/// </summary>
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; // 没有子页面
}
}
}
}
/// <summary>
/// 上层页面的编号堆栈,用于返回。
/// </summary>
public List<PageStackData> pageStack = new();
public class PageStackData
{
/// <summary>
/// <list type="bullet">
/// <item><description>CompDetail: (CompProject, ExpandedTitles, TargetVersion, TargetLoader, ResourceType)</description></item>
/// <item><description>VersionSaves: SavePath</description></item>
/// </list>
/// </summary>
public (
ModComp.CompProject CompProject,
List<string> 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<PageStackData>.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;
/// <summary>
/// 切换页面,并引起对应选择 UI 的改变。
/// </summary>
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);
}
}
/// <summary>
/// 通过点击导航栏改变页面。
/// </summary>
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();
}
/// <summary>
/// 通过点击返回按钮或手动触发返回来改变页面。
/// </summary>
public void PageBack()
{
if (pageStack.Any())
PageChangeActual(pageStack[0], PageSubType.Default);
else
PageChange(PageType.Launch);
}
// 实际处理页面切换
/// <summary>
/// 切换现有页面的实际方法。
/// </summary>
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");
}
/// <summary>
/// 退出子界面。
/// </summary>
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;
}
/// <summary>
/// 返回顶部。
/// </summary>
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
}