feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

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

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

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,2245 @@
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
}