From 0811b4f38297542d34b26351af687cb3bb07e493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E8=85=BE=E7=8C=AB=E8=B7=83?= <1043137532@qq.com> Date: Sun, 26 May 2024 17:42:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=AC=E5=BC=80=20ModMain.vb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 - Plain Craft Launcher 2/FormMain.xaml.vb | 2 +- Plain Craft Launcher 2/Modules/ModMain.vb | 877 ++++++++++++++++++++++ 3 files changed, 878 insertions(+), 3 deletions(-) create mode 100644 Plain Craft Launcher 2/Modules/ModMain.vb diff --git a/.gitignore b/.gitignore index efe225f..3ee9b08 100644 --- a/.gitignore +++ b/.gitignore @@ -351,5 +351,3 @@ PageOtherTest.* PageSetupUI.* # 开发者工具 ModDevelop.vb -# 不公开底层函数库,以免抄袭 -ModMain.vb diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index 2910887..5ed09e6 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -336,7 +336,7 @@ Public Class FormMain Setup.Load("UiBackgroundBlur") Setup.Load("UiLogoType") Setup.Load("UiHiddenPageDownload") - BackgroundRefresh(False, True) + PageSetupUI.BackgroundRefresh(False, True) MusicRefreshPlay(False, True) '扩展按钮 BtnExtraDownload.ShowCheck = AddressOf BtnExtraDownload_ShowCheck diff --git a/Plain Craft Launcher 2/Modules/ModMain.vb b/Plain Craft Launcher 2/Modules/ModMain.vb new file mode 100644 index 0000000..e2b797c --- /dev/null +++ b/Plain Craft Launcher 2/Modules/ModMain.vb @@ -0,0 +1,877 @@ +Imports System.Windows.Interop +Imports System.Windows.Threading + +Public Module ModMain + +#Region "弹出提示" + + ''' + ''' 提示信息的种类。 + ''' + Public Enum HintType + ''' + ''' 信息,通常是蓝色的“i”。 + ''' + ''' + Info + ''' + ''' 已完成,通常是绿色的“√”。 + ''' + ''' + Finish + ''' + ''' 错误,通常是红色的“×”。 + ''' + ''' + Critical + End Enum + Private Structure HintMessage + Public Text As String + Public Type As HintType + Public Log As Boolean + End Structure + + ''' + ''' 等待弹出的提示列表。以 {String, HintType, Log As Boolean} 形式存储为数组。 + ''' + Private HintWaiting As List(Of HintMessage) = If(HintWaiting, New List(Of HintMessage)) + ''' + ''' 在窗口左下角弹出提示文本。 + ''' + Public Sub Hint(Text As String, Optional Type As HintType = HintType.Info, Optional Log As Boolean = True) + If HintWaiting Is Nothing Then HintWaiting = New List(Of HintMessage) + HintWaiting.Add(New HintMessage With {.Text = If(Text, ""), .Type = Type, .Log = Log}) + End Sub + + Private Sub HintTick() + Try + + 'Tag 存储了:{ 是否可以重用, Uuid } + If Not HintWaiting.Any() Then Exit Sub + Do While HintWaiting.Any + ''清除空提示 + 'If IsNothing(HintWaiting(0)) OrElse IsNothing(HintWaiting(0)(0)) Then + ' HintWaiting.RemoveAt(0) + ' Continue Do + 'End If + Dim CurrentHint = HintWaiting(0) + '去回车 + CurrentHint.Text = CurrentHint.Text.Replace(vbCrLf, " ").Replace(vbCr, " ").Replace(vbLf, " ") + '超量提示直接忽略 + If FrmMain.PanHint.Children.Count >= 20 Then GoTo EndHint + '检查是否有重复提示 + Dim DoubleStack As Border = Nothing + For Each stack As Border In FrmMain.PanHint.Children + If stack.Tag(0) AndAlso CType(stack.Child, TextBlock).Text = CurrentHint.Text Then DoubleStack = stack + Next + '获取渐变颜色 + Dim TargetColor0, TargetColor1 As MyColor + Dim Percent As Double = 0.3 + Select Case CurrentHint.Type + Case HintType.Info + TargetColor0 = New MyColor(37, 155, 252) + TargetColor1 = New MyColor(10, 142, 252) + Case HintType.Finish + TargetColor0 = New MyColor(33, 177, 33) + TargetColor1 = New MyColor(29, 160, 29) + Case Else 'HintType.Critical + TargetColor0 = New MyColor(255, 53, 11) + TargetColor1 = New MyColor(255, 43, 0) + End Select + If Not IsNothing(DoubleStack) Then + '有重复提示,且该提示的进入动画已播放 + If Not AniIsRun("Hint Show " & DoubleStack.Tag(1)) Then + AniStop("Hint Hide " & DoubleStack.Tag(1)) + Dim Delay As Double = (800 + MathClamp(CurrentHint.Text.Length, 5, 23) * 180) * AniSpeed + AniStart({ + AaX(DoubleStack, -12 - DoubleStack.Margin.Left, 50,, New AniEaseOutFluent), + AaX(DoubleStack, -8, 50, 50, New AniEaseInFluent), + AaX(DoubleStack, 8, 50, 100, New AniEaseOutFluent), + AaX(DoubleStack, -8, 50, 150, New AniEaseInFluent), + AaDouble(Sub(i) + Percent += i + Dim Gradient As LinearGradientBrush = DoubleStack.Background + Gradient.GradientStops(0).Color = TargetColor0 * Percent + New MyColor(255, 255, 255) * (1 - Percent) + Gradient.GradientStops(1).Color = TargetColor1 * Percent + New MyColor(255, 255, 255) * (1 - Percent) + End Sub, 0.7, 250), + AaX(DoubleStack, -50, 200, Delay, New AniEaseInFluent), + AaOpacity(DoubleStack, -1, 150, Delay), + AaCode(Sub() DoubleStack.Tag(0) = False, Delay), + AaHeight(DoubleStack, -26, 100,, New AniEaseOutFluent, True), + AaCode(Sub() FrmMain.PanHint.Children.Remove(DoubleStack), , True) + }, "Hint Hide " & DoubleStack.Tag(1)) + End If + Else + '准备控件 + Dim NewHintControl As New Border With {.Tag = {True, GetUuid()}, .Margin = New Thickness(-70, 0, 20, 0), .Opacity = 0, .Height = 0, .HorizontalAlignment = HorizontalAlignment.Left, .CornerRadius = New CornerRadius(0, 6, 6, 0)} + NewHintControl.Background = New LinearGradientBrush(New GradientStopCollection(New List(Of GradientStop) From { + New GradientStop(TargetColor0 * Percent + New MyColor(255, 255, 255) * (1 - Percent), 0), + New GradientStop(TargetColor1 * Percent + New MyColor(255, 255, 255) * (1 - Percent), 1)}), 90) + NewHintControl.Child = New TextBlock With {.TextTrimming = TextTrimming.CharacterEllipsis, .FontSize = 13, .Text = CurrentHint.Text, .Foreground = New MyColor(255, 255, 255), .Margin = New Thickness(33, 5, 8, 5)} + 'AddHandler NewHintControl.MouseLeftButtonDown, AddressOf HideAllHint + FrmMain.PanHint.Children.Add(NewHintControl) + '控件动画 + Dim Animations As New List(Of AniData) + If FrmMain.PanHint.Children.Count > 1 Then + '已有提示 + Animations.Add(AaHeight(NewHintControl, 26, 150, , New AniEaseOutFluent)) + Else + '是唯一提示 + NewHintControl.Height = 26 + End If + '开始动画 + Animations.AddRange({ + AaX(NewHintControl, 30, 400, , New AniEaseOutElastic(AniEasePower.Weak)), + AaX(NewHintControl, 20, 200, , New AniEaseOutFluent), + AaOpacity(NewHintControl, 1, 100), + AaDouble(Sub(i) + Percent += i + Dim Gradient As LinearGradientBrush = NewHintControl.Background + Gradient.GradientStops(0).Color = TargetColor0 * Percent + New MyColor(255, 255, 255) * (1 - Percent) + Gradient.GradientStops(1).Color = TargetColor1 * Percent + New MyColor(255, 255, 255) * (1 - Percent) + End Sub, 0.7, 250, 100) + }) + AniStart(Animations, "Hint Show " & NewHintControl.Tag(1)) + '结束动画 + Dim Delay As Double = (800 + MathClamp(CurrentHint.Text.Length, 5, 23) * 180) * AniSpeed + AniStart({ + AaX(NewHintControl, -50, 200, Delay, New AniEaseInFluent), + AaOpacity(NewHintControl, -1, 150, Delay), + AaCode(Sub() NewHintControl.Tag(0) = False, Delay), + AaHeight(NewHintControl, -26, 100,, New AniEaseOutFluent, True), + AaCode(Sub() FrmMain.PanHint.Children.Remove(NewHintControl), , True) + }, "Hint Hide " & NewHintControl.Tag(1)) + End If + '结束处理 +EndHint: + If CurrentHint.Log Then Log("[UI] 弹出提示:" & CurrentHint.Text) + HintWaiting.RemoveAt(0) + Loop + Catch ex As Exception + Log(ex, "显示弹出提示失败", LogLevel.Normal) + End Try + End Sub + Private Sub HideAllHint() + For Each Control As Border In FrmMain.PanHint.Children + Control.IsHitTestVisible = False + AniStart({ + AaX(Control, -50, 200, , New AniEaseInFluent), + AaOpacity(Control, -1, 150, , New AniEaseInFluent), + AaCode(Sub() Control.Tag(0) = False), + AaHeight(Control, -26, 100,, New AniEaseOutFluent, True), + AaCode(Sub() FrmMain.PanHint.Children.Remove(Control), , True) + }, "Hint Hide " & Control.Tag(1)) + Next + End Sub + +#End Region + +#Region "弹窗" + + ''' + ''' 存储弹窗信息的转换器。 + ''' + Public Class MyMsgBoxConverter + Public Type As MyMsgBoxType + Public Title As String + Public Text As String + ''' + ''' 输入框模式:文本框的文本;选择模式:需要放进去的 List(Of MyListItem)。 + ''' + Public Content As Object + ''' + ''' 仅输入框模式:输入验证规则。 + ''' + Public ValidateRules As ObjectModel.Collection(Of Validate) + ''' + ''' 仅输入框模式:提示文本。 + ''' + Public HintText As String = "" + ''' + ''' 有多个按钮时,是否给第一个按钮加高亮。 + ''' + Public HighLight As Boolean + Public Button1 As String = "确定" + Public Button2 As String = "" + Public Button3 As String = "" + ''' + ''' 点击第一个按钮将执行该方法,不关闭弹窗。 + ''' + Public Button1Action As Action = Nothing + ''' + ''' 点击第二个按钮将执行该方法,不关闭弹窗。 + ''' + Public Button2Action As Action = Nothing + ''' + ''' 点击第三个按钮将执行该方法,不关闭弹窗。 + ''' + Public Button3Action As Action = Nothing + Public IsWarn As Boolean = False + Public ForceWait As Boolean = False + Public WaitFrame As New DispatcherFrame(True) + ''' + ''' 弹窗是否已经关闭。 + ''' + Public IsExited As Boolean = False + ''' + ''' 点击的按钮编号或输入的文本。若输入弹窗点击了非第一个按钮,则为 Nothing。 + ''' + Public Result As Object + End Class + Public Enum MyMsgBoxType + Text + [Select] + Input + End Enum + + ''' + ''' 显示弹窗,返回点击按钮的编号(从 1 开始)。 + ''' + ''' 弹窗的标题。 + ''' 弹窗的内容。 + ''' 显示的第一个按钮,默认为“确定”。 + ''' 显示的第二个按钮,默认为空。 + ''' 显示的第三个按钮,默认为空。 + ''' 点击第一个按钮将执行该方法,不关闭弹窗。 + ''' 点击第二个按钮将执行该方法,不关闭弹窗。 + ''' 点击第三个按钮将执行该方法,不关闭弹窗。 + ''' 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 + Public Function MyMsgBox(Caption As String, Optional Title As String = "提示", + Optional Button1 As String = "确定", Optional Button2 As String = "", Optional Button3 As String = "", + Optional IsWarn As Boolean = False, Optional HighLight As Boolean = True, Optional ForceWait As Boolean = False, + Optional Button1Action As Action = Nothing, Optional Button2Action As Action = Nothing, Optional Button3Action As Action = Nothing) As Integer + '将弹窗列入队列 + Dim Converter As New MyMsgBoxConverter With {.Type = MyMsgBoxType.Text, .Button1 = Button1, .Button2 = Button2, .Button3 = Button3, .Text = Caption, .IsWarn = IsWarn, .Title = Title, .HighLight = HighLight, .ForceWait = True, .Button1Action = Button1Action, .Button2Action = Button2Action, .Button3Action = Button3Action} + WaitingMyMsgBox.Add(Converter) + If RunInUi() Then + '若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗 + MyMsgBoxTick() + End If + If Button2.Length > 0 OrElse ForceWait Then + '若有多个按钮则开始等待 + If FrmMain Is Nothing OrElse FrmMain.PanMsg Is Nothing AndAlso RunInUi() Then + '主窗体尚未加载,用老土的弹窗来替代 + WaitingMyMsgBox.Remove(Converter) + If Button2.Length > 0 Then + Dim RawResult As MsgBoxResult = MsgBox(Caption, If(Button3.Length > 0, MsgBoxStyle.YesNoCancel, MsgBoxStyle.YesNo) + If(IsWarn, MsgBoxStyle.Critical, MsgBoxStyle.Question), Title) + Select Case RawResult + Case MsgBoxResult.Yes + Converter.Result = 1 + Case MsgBoxResult.No + Converter.Result = 2 + Case MsgBoxResult.Cancel + Converter.Result = 3 + End Select + Else + MsgBox(Caption, MsgBoxStyle.OkOnly + If(IsWarn, MsgBoxStyle.Critical, MsgBoxStyle.Question), Title) + Converter.Result = 1 + End If + Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" & Button1 & "," & Button2 & "," & Button3, LogLevel.Debug) + Else + Try + FrmMain.DragStop() + ComponentDispatcher.PushModal() + Dispatcher.PushFrame(Converter.WaitFrame) + Finally + ComponentDispatcher.PopModal() + End Try + End If + Log("[Control] 普通弹框返回:" & If(Converter.Result, "null")) + Return Converter.Result + Else + '不进行等待,直接返回 + Return 1 + End If + End Function + ''' + ''' 显示输入框并返回输入的文本。若点击第二个按钮,则返回 Nothing。 + ''' + ''' 弹窗的标题。 + ''' 文本框的输入检测。 + ''' 弹窗的介绍文本。 + ''' 文本框的默认内容。 + ''' 文本框的提示内容。 + ''' 显示的第一个按钮,默认为“确定”。 + ''' 显示的第二个按钮,默认为“取消”。 + ''' 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 + Public Function MyMsgBoxInput(Title As String, Optional Text As String = "", Optional DefaultInput As String = "", Optional ValidateRules As ObjectModel.Collection(Of Validate) = Nothing, Optional HintText As String = "", Optional Button1 As String = "确定", Optional Button2 As String = "取消", Optional IsWarn As Boolean = False) As String + '将弹窗列入队列 + Dim Converter As New MyMsgBoxConverter With {.Text = Text, .HintText = HintText, .Type = MyMsgBoxType.Input, .ValidateRules = If(ValidateRules, New ObjectModel.Collection(Of Validate)), .Button1 = Button1, .Button2 = Button2, .Content = DefaultInput, .IsWarn = IsWarn, .Title = Title} + WaitingMyMsgBox.Add(Converter) + '虽然我也不知道这是啥但是能用就成了 :) + Try + If FrmMain IsNot Nothing Then FrmMain.DragStop() + ComponentDispatcher.PushModal() + Dispatcher.PushFrame(Converter.WaitFrame) + Finally + ComponentDispatcher.PopModal() + End Try + Log("[Control] 输入弹框返回:" & If(Converter.Result, "null")) + Return Converter.Result + End Function + ''' + ''' 显示选择框并返回选择的第几项(从 0 开始)。若点击第二个按钮,则返回 Nothing。 + ''' + ''' 弹窗的标题。 + ''' 显示的第一个按钮,默认为 “确定”。 + ''' 显示的第二个按钮,默认为空。 + ''' 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 + Public Function MyMsgBoxSelect(Selections As List(Of IMyRadio), Optional Title As String = "提示", Optional Button1 As String = "确定", Optional Button2 As String = "", Optional IsWarn As Boolean = False) As Integer? + '将弹窗列入队列 + Dim Converter As New MyMsgBoxConverter With {.Type = MyMsgBoxType.Select, .Button1 = Button1, .Button2 = Button2, .Content = Selections, .IsWarn = IsWarn, .Title = Title} + WaitingMyMsgBox.Add(Converter) + '虽然我也不知道这是啥但是能用就成了 :) + Try + If FrmMain IsNot Nothing Then FrmMain.DragStop() + ComponentDispatcher.PushModal() + Dispatcher.PushFrame(Converter.WaitFrame) + Finally + ComponentDispatcher.PopModal() + End Try + Log("[Control] 选择弹框返回:" & If(Converter.Result, "null")) + Return Converter.Result + End Function + + ''' + ''' 等待显示的弹窗。 + ''' + Public WaitingMyMsgBox As List(Of MyMsgBoxConverter) = If(WaitingMyMsgBox, New List(Of MyMsgBoxConverter)) + Public Sub MyMsgBoxTick() + Try + If FrmMain Is Nothing OrElse FrmMain.PanMsg Is Nothing OrElse FrmMain.WindowState = WindowState.Minimized Then Exit Sub + If FrmMain.PanMsg.Children.Count > 0 Then + '弹窗中 + FrmMain.PanMsg.Visibility = Visibility.Visible + ElseIf WaitingMyMsgBox.Any Then + '没有弹窗,显示一个等待的弹窗 + FrmMain.PanMsg.Visibility = Visibility.Visible + Select Case CType(WaitingMyMsgBox(0), MyMsgBoxConverter).Type + Case MyMsgBoxType.Input + FrmMain.PanMsg.Children.Add(New MyMsgInput(WaitingMyMsgBox(0))) + Case MyMsgBoxType.Select + FrmMain.PanMsg.Children.Add(New MyMsgSelect(WaitingMyMsgBox(0))) + Case MyMsgBoxType.Text + FrmMain.PanMsg.Children.Add(New MyMsgText(WaitingMyMsgBox(0))) + End Select + WaitingMyMsgBox.RemoveAt(0) + Else + '没有弹窗,没有等待的弹窗 + If Not FrmMain.PanMsg.Visibility = Visibility.Collapsed Then FrmMain.PanMsg.Visibility = Visibility.Collapsed + End If + Catch ex As Exception + Log(ex, "处理等待中的弹窗失败", LogLevel.Feedback) + End Try + End Sub + +#End Region + +#Region "页面声明" + '在最后进行页面声明,避免颜色尚未加载完毕 + + '窗体声明 + Public FrmMain As FormMain + Public FrmStart As SplashScreen + + '页面声明(出于单元测试考虑,初始化页面已转入 FormMain 中) + Public FrmLaunchLeft As PageLaunchLeft + Public FrmLaunchRight As PageLaunchRight + Public FrmSelectLeft As PageSelectLeft + Public FrmSelectRight As PageSelectRight + Public FrmSpeedLeft As PageSpeedLeft + Public FrmSpeedRight As PageSpeedRight + + '联机页面声明 + Public FrmLinkLeft As PageLinkLeft + Public FrmLinkIoi As PageLinkIoi + Public FrmLinkHiper As PageLinkHiper + Public FrmLinkHelp As PageOtherHelpDetail + Public FrmLinkFeedback As PageLinkFeedback + + '下载页面声明 + Public FrmDownloadLeft As PageDownloadLeft + Public FrmDownloadInstall As PageDownloadInstall + Public FrmDownloadClient As PageDownloadClient + Public FrmDownloadOptiFine As PageDownloadOptiFine + Public FrmDownloadLiteLoader As PageDownloadLiteLoader + Public FrmDownloadForge As PageDownloadForge + Public FrmDownloadFabric As PageDownloadFabric + Public FrmDownloadMod As PageDownloadMod + Public FrmDownloadPack As PageDownloadPack + + '设置页面声明 + Public FrmSetupLeft As PageSetupLeft + Public FrmSetupLaunch As PageSetupLaunch + Public FrmSetupUI As PageSetupUI + Public FrmSetupSystem As PageSetupSystem + Public FrmSetupLink As PageSetupLink + + '其他页面声明 + Public FrmOtherLeft As PageOtherLeft + Public FrmOtherHelp As PageOtherHelp + Public FrmOtherAbout As PageOtherAbout + Public FrmOtherTest As PageOtherTest + + '登录页面声明 + Public FrmLoginLegacy As PageLoginLegacy + Public FrmLoginNide As PageLoginNide + Public FrmLoginNideSkin As PageLoginNideSkin + Public FrmLoginAuth As PageLoginAuth + Public FrmLoginAuthSkin As PageLoginAuthSkin + Public FrmLoginMs As PageLoginMs + Public FrmLoginMsSkin As PageLoginMsSkin + + '版本设置页面声明 + Public FrmVersionLeft As PageVersionLeft + Public FrmVersionOverall As PageVersionOverall + Public FrmVersionMod As PageVersionMod + Public FrmVersionModDisabled As PageVersionModDisabled + Public FrmVersionSetup As PageVersionSetup + + '资源信息分页声明 + Public FrmDownloadCompDetail As PageDownloadCompDetail + +#End Region + +#Region "帮助" + + Public Class HelpEntry + + '基础 + + ''' + ''' 显示标题。 + ''' + Public Title As String + ''' + ''' 显示描述。 + ''' + Public Desc As String + ''' + ''' 检索关键字。 + ''' + Public Search As String + ''' + ''' 用于分类的标签列表。 + ''' + Public Types As List(Of String) + + '显示(可选) + + ''' + ''' 帮助项的自定义图标。可能为 Nothing。 + ''' + Public Logo As String = Nothing + ''' + ''' 是否显示在搜索结果。默认为 True。 + ''' + Public ShowInSearch As Boolean = True + ''' + ''' 是否在公开版的 PCL 中显示(这会影响主页与搜索)。默认为 True。 + ''' + Public ShowInPublic As Boolean = True + ''' + ''' 是否在快照版的 PCL 中显示(这会影响主页与搜索)。默认为 True。 + ''' + Public ShowInSnapshot As Boolean = True + + '动作 + + ''' + ''' 是否为 “执行事件”。 + ''' + Public IsEvent As Boolean + Public EventType As String + Public EventData As String + ''' + ''' 若非执行事件,其对应的 .xaml 本地文件内容。 + ''' + Public XamlContent As String + + '转换 + + ''' + ''' 从文件初始化 HelpEntry 对象,失败会抛出异常。 + ''' + Public Sub New(FilePath As String) + Dim JsonData As JObject = GetJson(HelpArgumentReplace(ReadFile(FilePath))) + If JsonData Is Nothing Then Throw New FileNotFoundException("未找到帮助文件:" & FilePath, FilePath) + '加载常规信息 + If JsonData("Title") IsNot Nothing Then + Title = JsonData("Title") + Else + Throw New ArgumentException("未找到 Title 项") + End If + Desc = If(JsonData("Description"), "") + Search = If(JsonData("Keywords"), "") + Logo = JsonData("Logo") '为保持 Nothing,不要加 If + ShowInSearch = If(JsonData("ShowInSearch"), ShowInSearch) + ShowInPublic = If(JsonData("ShowInPublic"), ShowInPublic) + ShowInSnapshot = If(JsonData("ShowInSnapshot"), ShowInSnapshot) + Types = New List(Of String) + For Each NameOfType In If(JsonData("Types"), GetJson("[]")) + Types.Add(NameOfType) + Next + '加载事件信息 + If If(JsonData("IsEvent"), False) Then + EventType = JsonData("EventType") + If EventType Is Nothing Then Throw New ArgumentException("未找到 EventType 项") + EventData = If(JsonData("EventData"), "") + IsEvent = True + Else + Dim XamlAddress As String = FilePath.ToLower.Replace(".json", ".xaml") + If File.Exists(XamlAddress) Then + XamlContent = ReadFile(XamlAddress) + IsEvent = False + Else + Throw New FileNotFoundException("未找到帮助条目 .json 对应的 .xaml 文件(" & XamlAddress & ")") + End If + End If + End Sub + ''' + ''' 获取该 HelpEntry 对应的 MyListItem。 + ''' + Public Function ToListItem() As MyListItem + Return SetToListItem(New MyListItem) + End Function + ''' + ''' 将属性设置入一个现有的 ListItem。 + ''' + Public Function SetToListItem(Item As MyListItem) As MyListItem + Dim Logo As String + If IsEvent Then + If EventType = "弹出窗口" Then + Logo = PathImage & "Blocks/GrassPath.png" + Else + Logo = PathImage & "Blocks/CommandBlock.png" + End If + Else + Logo = PathImage & "Blocks/Grass.png" + End If + '设置属性 + With Item + .SnapsToDevicePixels = True + .Title = Title + .Info = Desc + .Logo = If(Me.Logo, Logo) + .Height = 42 + .Type = MyListItem.CheckType.Clickable + .Tag = Me + .EventType = Nothing + .EventData = Nothing + End With + '项目的点击事件 + AddHandler Item.Click, Sub(sender, e) PageOtherHelp.OnItemClick(sender.Tag) + Return Item + End Function + + End Class + + Public HelpLoader As New LoaderTask(Of Integer, List(Of HelpEntry))("Help Page", AddressOf HelpLoad,, ThreadPriority.BelowNormal) + Private ReadOnly HelpLoadLock As New Object + ''' + ''' 初始化帮助列表对象。 + ''' + Private Sub HelpLoad(Loader As LoaderTask(Of Integer, List(Of HelpEntry))) + SyncLock HelpLoadLock '避免重复解压文件导致出错 + Try + + '解压内置文件 + HelpTryExtract() + + '遍历文件 + Dim FileList As New List(Of String) + Try + Dim IgnoreList As New List(Of String) + '读取自定义文件 + If Directory.Exists(Path & "PCL\Help\") Then + For Each File In EnumerateFiles(Path & "PCL\Help\") + Select Case File.Extension.ToLower + Case ".helpignore" + '加载忽略列表 + Log("[Help] 发现 .helpignore 文件:" & File.FullName) + For Each Line In ReadFile(File.FullName).Split(vbCrLf.ToCharArray) + Dim RealString As String = Line.Before("#").Trim + If String.IsNullOrWhiteSpace(RealString) Then Continue For + IgnoreList.Add(RealString) + If ModeDebug Then Log("[Help] > " & RealString) + Next + Case ".json" + FileList.Add(File.FullName) + End Select + Next + End If + Log("[Help] 已扫描 PCL 文件夹下的帮助文件,目前总计 " & FileList.Count & " 条") + '读取自带文件 + For Each File In EnumerateFiles(PathTemp & "Help") + '跳过非 json 文件与以 . 开头的文件夹 + If File.Extension.ToLower <> ".json" OrElse File.Directory.FullName.Replace(PathTemp & "Help", "").Contains("\.") Then Continue For + '检查忽略列表 + Dim RealPath As String = File.FullName.Replace(PathTemp & "Help\", "") + For Each Ignore In IgnoreList + If RegexCheck(RealPath, Ignore) Then + If ModeDebug Then Log("[Help] 已忽略 " & RealPath & ":" & Ignore) + GoTo NextFile + End If + Next + FileList.Add(File.FullName) +NextFile: + Next + Log("[Help] 已扫描缓存文件夹下的帮助文件,目前总计 " & FileList.Count & " 条") + Catch ex As Exception + Log(ex, "检查帮助文件夹失败", LogLevel.Msgbox) + End Try + If Loader.IsAborted Then Exit Sub + + '将文件实例化 + Dim Dict As New List(Of HelpEntry) + For Each FilePath As String In FileList + Try + Dim Entry As New HelpEntry(FilePath) + Dict.Add(Entry) + If ModeDebug Then Log("[Help] 已加载的帮助条目:" & Entry.Title & " ← " & FilePath) + Catch ex As Exception + Log(ex, "初始化帮助条目失败(" & FilePath & ")", LogLevel.Msgbox) + End Try + Next + + '回设 + If Not Dict.Any() Then Throw New Exception("未找到可用的帮助;若不需要帮助页面,可以在 设置 → 个性化 → 功能隐藏 中将其隐藏") + If Loader.IsAborted Then Exit Sub + Loader.Output = Dict + + Catch ex As Exception + Log(ex, "帮助列表初始化失败") + Throw + End Try + End SyncLock + End Sub + ''' + ''' 尝试解压内置帮助文件。 + ''' + Public Sub HelpTryExtract() + If Setup.Get("SystemHelpVersion") <> VersionCode OrElse Not File.Exists(PathTemp & "Help\启动器\备份设置.xaml") Then + DeleteDirectory(PathTemp & "Help") + Directory.CreateDirectory(PathTemp & "Help") + WriteFile(PathTemp & "Cache\Help.zip", GetResources("Help")) + ExtractFile(PathTemp & "Cache\Help.zip", PathTemp & "Help", Encoding.UTF8) + Setup.Set("SystemHelpVersion", VersionCode) + Log("[Help] 已解压内置帮助文件,目前状态:" & File.Exists(PathTemp & "Help\启动器\备份设置.xaml"), LogLevel.Debug) + End If + End Sub + ''' + ''' 对帮助文件约定的替换标记进行处理,如果遇到需要转义的字符会进行转义。 + ''' + Public Function HelpArgumentReplace(Xaml As String) As String + Dim Result = Xaml.Replace("{path}", EscapeXML(Path)) + Result = RegexReplaceEach(Result, Function() EscapeXML(PageOtherTest.GetRandomHint()), "\{hint\}") + Result = RegexReplaceEach(Result, Function() EscapeXML(PageOtherTest.GetRandomCave()), "\{cave\}") + Return Result + End Function + +#End Region + +#Region "愚人节" + + Public IsAprilEnabled As Boolean = Date.Now.Month = 4 AndAlso Date.Now.Day = 1 + Public IsAprilGiveup As Boolean = False + Private AprilSpeed As New Vector(0, 0) + Private AprilIdieCount As Integer = 0, AprilMousePosLast As New Point(0, 0) + Private AprilDistance As Integer = 0 + Private Sub TimerFool() + Try + If FrmLaunchLeft Is Nothing OrElse FrmLaunchLeft.AprilPosTrans Is Nothing OrElse FrmMain.lastMouseArg Is Nothing Then Exit Sub + If IsAprilGiveup OrElse FrmMain.PageCurrent <> FormMain.PageType.Launch OrElse AniControlEnabled <> 0 OrElse Not FrmLaunchLeft.BtnLaunch.IsLoaded Then Exit Sub + + '计算是否空闲 + Dim MousePos = FrmMain.lastMouseArg.GetPosition(FrmMain) + If MousePos = AprilMousePosLast Then + AprilIdieCount += 1 + Else + AprilMousePosLast = MousePos + AprilIdieCount = 0 + End If + '计算躲避移动 + Dim Direction As Vector + Dim Distance As Double + Dim ButtonWidth = FrmLaunchLeft.BtnLaunch.ActualWidth / 2, ButtonHeight = FrmLaunchLeft.BtnLaunch.ActualHeight / 2 + Dim Vec As Vector = FrmMain.lastMouseArg.GetPosition(FrmLaunchLeft.BtnLaunch) - New Vector(ButtonWidth, ButtonHeight) + Dim Dir As New Vector(Vec.X, Vec.Y) + Dir.Normalize() + Direction = -Dir + Distance = New Vector(Math.Max(0, Math.Abs(Vec.X) - ButtonWidth), Math.Max(0, Math.Abs(Vec.Y) - ButtonHeight)).Length + Dim BreathScale = Math.Sin(Timer150Count / 37.5 * Math.PI) + Dim Acc = Math.Max(0, BreathScale * 0.25 - 0.65 - Math.Log((Distance + 0.4) / 200)) * Direction '加速度 + '计算回归移动 + If AprilIdieCount >= 64 * 5 Then + Dim SafeDist As Vector = FrmMain.lastMouseArg.GetPosition(FrmMain.PanMain) - New Vector(ButtonWidth, FrmMain.PanMain.ActualHeight - ButtonHeight * 3) + Dim Back As New Vector(FrmLaunchLeft.AprilPosTrans.X, FrmLaunchLeft.AprilPosTrans.Y) + If SafeDist.Length > 250 AndAlso Back.Length > 0.4 Then + Acc -= Back * 0.0005 + Back.Normalize() + Acc -= Back * 0.15 + End If + End If + '回到边界 + Dim Relative As Point = FrmLaunchLeft.BtnLaunch.TranslatePoint(New Point(0, 0), FrmMain.PanForm) + If Relative.X < -ButtonWidth * 2 Then + FrmLaunchLeft.AprilPosTrans.X += FrmMain.PanForm.ActualWidth + ButtonWidth * 2 '离开左边界 + AprilSpeed.X -= 80 + If Relative.Y < 0 Then + FrmLaunchLeft.AprilPosTrans.Y += ButtonHeight * 2.5 + ElseIf Relative.Y > FrmMain.PanForm.ActualHeight - ButtonHeight * 2 Then + FrmLaunchLeft.AprilPosTrans.Y -= ButtonHeight * 2.5 + End If + ElseIf Relative.X > FrmMain.PanForm.ActualWidth Then + FrmLaunchLeft.AprilPosTrans.X -= FrmMain.PanForm.ActualWidth + ButtonWidth * 2 '离开右边界 + AprilSpeed.X += 80 + If Relative.Y < 0 Then + FrmLaunchLeft.AprilPosTrans.Y += ButtonHeight * 2.5 + ElseIf Relative.Y > FrmMain.PanForm.ActualHeight - ButtonHeight * 2 Then + FrmLaunchLeft.AprilPosTrans.Y -= ButtonHeight * 2.5 + End If + ElseIf Relative.Y < -ButtonHeight * 2 Then + FrmLaunchLeft.AprilPosTrans.Y += FrmMain.PanForm.ActualHeight + ButtonHeight * 2 '离开上边界 + AprilSpeed.Y -= 25 + If Relative.X < 0 Then + FrmLaunchLeft.AprilPosTrans.X += ButtonWidth * 2 + ElseIf Relative.X > FrmMain.PanForm.ActualWidth - ButtonWidth * 2 Then + FrmLaunchLeft.AprilPosTrans.X -= ButtonWidth * 2 + End If + ElseIf Relative.Y > FrmMain.PanForm.ActualHeight Then + FrmLaunchLeft.AprilPosTrans.Y -= FrmMain.PanForm.ActualHeight + ButtonHeight * 2 '离开下边界 + AprilSpeed.Y += 25 + If Relative.X < 0 Then + FrmLaunchLeft.AprilPosTrans.X += ButtonWidth * 2 + ElseIf Relative.X > FrmMain.PanForm.ActualWidth - ButtonWidth * 2 Then + FrmLaunchLeft.AprilPosTrans.X -= ButtonWidth * 2 + End If + End If + '移动 + AprilSpeed = AprilSpeed * 0.8 + Acc + Dim SpeedValue = Math.Min(60, AprilSpeed.Length) + If SpeedValue < 0.01 Then Exit Sub + AprilSpeed.Normalize() + AprilSpeed *= SpeedValue + AprilDistance += SpeedValue + FrmLaunchLeft.AprilPosTrans.X += AprilSpeed.X + FrmLaunchLeft.AprilPosTrans.Y += AprilSpeed.Y + '大小改变 + FrmLaunchLeft.AprilScaleTrans.ScaleX = MathClamp(1 - (Math.Abs(Direction.X) - Math.Abs(Direction.Y)) * (SpeedValue / 160), 0.2, 1.8) + FrmLaunchLeft.AprilScaleTrans.ScaleY = MathClamp(1 - (Math.Abs(Direction.Y) - Math.Abs(Direction.X)) * (SpeedValue / 100), 0.2, 1.8) + '放弃提示 + If AprilDistance > 4000 Then + AprilDistance = -4000 + Select Case RandomInteger(0, 3) + Case 0 + Hint("放弃吧!只需要点一下右下角的小白旗……") + Case 1 + Hint("看到右下角的那面小白旗了吗?") + Case 2 + Hint("这里建议点一下右下角的小白旗投降呢.jpg") + Case 3 + Hint("右下角的小白旗永远等着你……") + End Select + End If + + Catch ex As Exception + Log(ex, "愚人节移动出错", LogLevel.Feedback) + End Try + End Sub + +#End Region + +#Region "系统" + + ''' + ''' 把某个 PCL 窗口拖到最前面。 + ''' + Public Sub ShowWindowToTop(Handle As IntPtr) + Try + PostMessage(Handle, 400 * 16 + 2, 0, 0) + SetForegroundWindow(Handle) '不在这里放不行,神秘 WinAPI,建议别动 + Catch ex As Exception + Log(ex, "设置窗口置顶失败", LogLevel.Hint) + End Try + End Sub + Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ClassName As String, WindowName As String) As IntPtr + Public Declare Function SetForegroundWindow Lib "user32" (hWnd As IntPtr) As Integer + Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (hWnd As IntPtr, msg As UInteger, wParam As Long, lParam As Long) As Boolean + +#End Region + + Public DragControl = Nothing + Private Timer4Count As Integer = 0 + Private Timer150Count As Integer = 0 + Private Sub TimerMain() + Try +#Region "每 50ms 执行一次的代码" + HintTick() + MyMsgBoxTick() + FrmMain.DragTick() + LoaderTaskbarProgressRefresh() + If ThemeDontClick = 2 Then ThemeRefresh() +#End Region + Catch ex As Exception + Log(ex, "短程主时钟执行异常", LogLevel.Assert) + End Try + Timer4Count += 1 + If Timer4Count = 4 Then + Timer4Count = 0 + Try +#Region "每 250ms 执行一次的代码" + If ThemeNow = 12 Then ThemeRefresh() +#End Region + Catch ex As Exception + Log(ex, "中程主时钟执行异常", LogLevel.Debug) + End Try + End If + Timer150Count += 1 + If Timer150Count = 150 Then + Timer150Count = 0 + Try +#Region "每 7.5s 执行一次的代码" + If FrmMain.BtnExtraApril_ShowCheck AndAlso AprilDistance <> 0 Then FrmMain.BtnExtraApril.Ribble() + '以未知原因窗口被丢到一边去的修复(Top、Left = -25600),还有 #745 + RunInUi(Sub() + If Not FrmMain.Hidden Then + If FrmMain.Top < -9000 Then FrmMain.Top = 100 + If FrmMain.Left < -9000 Then FrmMain.Left = 100 '窗口拉至最大时 Left = -18.8 + End If + End Sub) +#End Region + Catch ex As Exception + Log(ex, "长程主时钟执行异常", LogLevel.Assert) + End Try + End If + End Sub + Public Sub TimerMainStart() + RunInNewThread( + Sub() + Try + Do While True + RunInUiWait(AddressOf TimerMain) + Thread.Sleep(50 * 0.98) + Loop + Catch ex As Exception + Log(ex, "程序主时钟出错", LogLevel.Feedback) + End Try + End Sub, "Timer Main") + If Not IsAprilEnabled Then Exit Sub + RunInNewThread( + Sub() + Try + Dim LastTime = My.Computer.Clock.TickCount + Do While True + If LastTime <> My.Computer.Clock.TickCount Then + LastTime = My.Computer.Clock.TickCount + RunInUiWait(AddressOf TimerFool) + End If + Thread.Sleep(1) + Loop + Catch ex As Exception + Log(ex, "愚人节主时钟出错", LogLevel.Feedback) + End Try + End Sub, "Timer Main Fool") + End Sub + +End Module