ModMain.vb 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. Imports System.Windows.Interop
  2. Imports System.Windows.Threading
  3. Public Module ModMain
  4. #Region "弹出提示"
  5. ''' <summary>
  6. ''' 提示信息的种类。
  7. ''' </summary>
  8. Public Enum HintType
  9. ''' <summary>
  10. ''' 信息,通常是蓝色的“i”。
  11. ''' </summary>
  12. ''' <remarks></remarks>
  13. Info
  14. ''' <summary>
  15. ''' 已完成,通常是绿色的“√”。
  16. ''' </summary>
  17. ''' <remarks></remarks>
  18. Finish
  19. ''' <summary>
  20. ''' 错误,通常是红色的“×”。
  21. ''' </summary>
  22. ''' <remarks></remarks>
  23. Critical
  24. End Enum
  25. Private Structure HintMessage
  26. Public Text As String
  27. Public Type As HintType
  28. Public Log As Boolean
  29. End Structure
  30. ''' <summary>
  31. ''' 等待弹出的提示列表。以 {String, HintType, Log As Boolean} 形式存储为数组。
  32. ''' </summary>
  33. Private HintWaiting As List(Of HintMessage) = If(HintWaiting, New List(Of HintMessage))
  34. ''' <summary>
  35. ''' 在窗口左下角弹出提示文本。
  36. ''' </summary>
  37. Public Sub Hint(Text As String, Optional Type As HintType = HintType.Info, Optional Log As Boolean = True)
  38. If HintWaiting Is Nothing Then HintWaiting = New List(Of HintMessage)
  39. HintWaiting.Add(New HintMessage With {.Text = If(Text, ""), .Type = Type, .Log = Log})
  40. End Sub
  41. Private Sub HintTick()
  42. Try
  43. 'Tag 存储了:{ 是否可以重用, Uuid }
  44. If Not HintWaiting.Any() Then Exit Sub
  45. Do While HintWaiting.Any
  46. ''清除空提示
  47. 'If IsNothing(HintWaiting(0)) OrElse IsNothing(HintWaiting(0)(0)) Then
  48. ' HintWaiting.RemoveAt(0)
  49. ' Continue Do
  50. 'End If
  51. Dim CurrentHint = HintWaiting(0)
  52. '去回车
  53. CurrentHint.Text = CurrentHint.Text.Replace(vbCrLf, " ").Replace(vbCr, " ").Replace(vbLf, " ")
  54. '超量提示直接忽略
  55. If FrmMain.PanHint.Children.Count >= 20 Then GoTo EndHint
  56. '检查是否有重复提示
  57. Dim DoubleStack As Border = Nothing
  58. For Each stack As Border In FrmMain.PanHint.Children
  59. If stack.Tag(0) AndAlso CType(stack.Child, TextBlock).Text = CurrentHint.Text Then DoubleStack = stack
  60. Next
  61. '获取渐变颜色
  62. Dim TargetColor0, TargetColor1 As MyColor
  63. Dim Percent As Double = 0.3
  64. Select Case CurrentHint.Type
  65. Case HintType.Info
  66. TargetColor0 = New MyColor(215, 37, 155, 252)
  67. TargetColor1 = New MyColor(215, 10, 142, 252)
  68. Case HintType.Finish
  69. TargetColor0 = New MyColor(215, 33, 177, 33)
  70. TargetColor1 = New MyColor(215, 29, 160, 29)
  71. Case Else 'HintType.Critical
  72. TargetColor0 = New MyColor(215, 255, 53, 11)
  73. TargetColor1 = New MyColor(215, 255, 43, 0)
  74. End Select
  75. If Not IsNothing(DoubleStack) Then
  76. '有重复提示,且该提示的进入动画已播放
  77. If Not AniIsRun("Hint Show " & DoubleStack.Tag(1)) Then
  78. AniStop("Hint Hide " & DoubleStack.Tag(1))
  79. Dim Delay As Double = (800 + MathClamp(CurrentHint.Text.Length, 5, 23) * 180) * AniSpeed
  80. AniStart({
  81. AaX(DoubleStack, -12 - DoubleStack.Margin.Left, 50,, New AniEaseOutFluent),
  82. AaX(DoubleStack, -8, 50, 50, New AniEaseInFluent),
  83. AaX(DoubleStack, 8, 50, 100, New AniEaseOutFluent),
  84. AaX(DoubleStack, -8, 50, 150, New AniEaseInFluent),
  85. AaDouble(Sub(i)
  86. Percent += i
  87. Dim Gradient As LinearGradientBrush = DoubleStack.Background
  88. Gradient.GradientStops(0).Color = TargetColor0 * Percent + New MyColor(255, 255, 255) * (1 - Percent)
  89. Gradient.GradientStops(1).Color = TargetColor1 * Percent + New MyColor(255, 255, 255) * (1 - Percent)
  90. End Sub, 0.7, 250),
  91. AaX(DoubleStack, -50, 200, Delay, New AniEaseInFluent),
  92. AaOpacity(DoubleStack, -1, 150, Delay),
  93. AaCode(Sub() DoubleStack.Tag(0) = False, Delay),
  94. AaHeight(DoubleStack, -26, 100,, New AniEaseOutFluent, True),
  95. AaCode(Sub() FrmMain.PanHint.Children.Remove(DoubleStack), , True)
  96. }, "Hint Hide " & DoubleStack.Tag(1))
  97. End If
  98. Else
  99. '准备控件
  100. 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)}
  101. NewHintControl.Background = New LinearGradientBrush(New GradientStopCollection(New List(Of GradientStop) From {
  102. New GradientStop(TargetColor0 * Percent + New MyColor(255, 255, 255) * (1 - Percent), 0),
  103. New GradientStop(TargetColor1 * Percent + New MyColor(255, 255, 255) * (1 - Percent), 1)}), 90)
  104. 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)}
  105. 'AddHandler NewHintControl.MouseLeftButtonDown, AddressOf HideAllHint
  106. FrmMain.PanHint.Children.Add(NewHintControl)
  107. '控件动画
  108. Dim Animations As New List(Of AniData)
  109. If FrmMain.PanHint.Children.Count > 1 Then
  110. '已有提示
  111. Animations.Add(AaHeight(NewHintControl, 26, 150, , New AniEaseOutFluent))
  112. Else
  113. '是唯一提示
  114. NewHintControl.Height = 26
  115. End If
  116. '开始动画
  117. Animations.AddRange({
  118. AaX(NewHintControl, 30, 400, , New AniEaseOutElastic(AniEasePower.Weak)),
  119. AaX(NewHintControl, 20, 200, , New AniEaseOutFluent),
  120. AaOpacity(NewHintControl, 1, 100),
  121. AaDouble(Sub(i)
  122. Percent += i
  123. Dim Gradient As LinearGradientBrush = NewHintControl.Background
  124. Gradient.GradientStops(0).Color = TargetColor0 * Percent + New MyColor(255, 255, 255) * (1 - Percent)
  125. Gradient.GradientStops(1).Color = TargetColor1 * Percent + New MyColor(255, 255, 255) * (1 - Percent)
  126. End Sub, 0.7, 250, 100)
  127. })
  128. AniStart(Animations, "Hint Show " & NewHintControl.Tag(1))
  129. '结束动画
  130. Dim Delay As Double = (800 + MathClamp(CurrentHint.Text.Length, 5, 23) * 180) * AniSpeed
  131. AniStart({
  132. AaX(NewHintControl, -50, 200, Delay, New AniEaseInFluent),
  133. AaOpacity(NewHintControl, -1, 150, Delay),
  134. AaCode(Sub() NewHintControl.Tag(0) = False, Delay),
  135. AaHeight(NewHintControl, -26, 100,, New AniEaseOutFluent, True),
  136. AaCode(Sub() FrmMain.PanHint.Children.Remove(NewHintControl), , True)
  137. }, "Hint Hide " & NewHintControl.Tag(1))
  138. End If
  139. '结束处理
  140. EndHint:
  141. If CurrentHint.Log Then Log("[UI] 弹出提示:" & CurrentHint.Text)
  142. HintWaiting.RemoveAt(0)
  143. Loop
  144. Catch ex As Exception
  145. Log(ex, "显示弹出提示失败", LogLevel.Normal)
  146. End Try
  147. End Sub
  148. Private Sub HideAllHint()
  149. For Each Control As Border In FrmMain.PanHint.Children
  150. Control.IsHitTestVisible = False
  151. AniStart({
  152. AaX(Control, -50, 200, , New AniEaseInFluent),
  153. AaOpacity(Control, -1, 150, , New AniEaseInFluent),
  154. AaCode(Sub() Control.Tag(0) = False),
  155. AaHeight(Control, -26, 100,, New AniEaseOutFluent, True),
  156. AaCode(Sub() FrmMain.PanHint.Children.Remove(Control), , True)
  157. }, "Hint Hide " & Control.Tag(1))
  158. Next
  159. End Sub
  160. #End Region
  161. #Region "弹窗"
  162. ''' <summary>
  163. ''' 存储弹窗信息的转换器。
  164. ''' </summary>
  165. Public Class MyMsgBoxConverter
  166. Public Type As MyMsgBoxType
  167. Public Title As String
  168. Public Text As String
  169. ''' <summary>
  170. ''' 输入模式:文本框的文本。
  171. ''' 选择模式:需要放进去的 List(Of MyListItem)。
  172. ''' 登录模式:登录步骤 1 中返回的 JSON。
  173. ''' </summary>
  174. Public Content As Object
  175. ''' <summary>
  176. ''' 输入模式:输入验证规则。
  177. ''' </summary>
  178. Public ValidateRules As ObjectModel.Collection(Of Validate)
  179. ''' <summary>
  180. ''' 输入模式:提示文本。
  181. ''' </summary>
  182. Public HintText As String = ""
  183. ''' <summary>
  184. ''' 有多个按钮时,是否给第一个按钮加高亮。
  185. ''' </summary>
  186. Public HighLight As Boolean
  187. Public Button1 As String = "确定"
  188. Public Button2 As String = ""
  189. Public Button3 As String = ""
  190. ''' <summary>
  191. ''' 点击第一个按钮将执行该方法,不关闭弹窗。
  192. ''' </summary>
  193. Public Button1Action As Action = Nothing
  194. ''' <summary>
  195. ''' 点击第二个按钮将执行该方法,不关闭弹窗。
  196. ''' </summary>
  197. Public Button2Action As Action = Nothing
  198. ''' <summary>
  199. ''' 点击第三个按钮将执行该方法,不关闭弹窗。
  200. ''' </summary>
  201. Public Button3Action As Action = Nothing
  202. Public IsWarn As Boolean = False
  203. Public ForceWait As Boolean = False
  204. Public WaitFrame As New DispatcherFrame(True)
  205. ''' <summary>
  206. ''' 弹窗是否已经关闭。
  207. ''' </summary>
  208. Public IsExited As Boolean = False
  209. ''' <summary>
  210. ''' 输入模式:输入的文本。若点击了 非 第一个按钮,则为 Nothing。
  211. ''' 选择模式:点击的按钮编号,从 1 开始。
  212. ''' 登录模式:字符串数组 {AccessToken, RefreshToken} 或一个 Exception。
  213. ''' </summary>
  214. Public Result As Object
  215. End Class
  216. Public Enum MyMsgBoxType
  217. Text
  218. [Select]
  219. Input
  220. Login
  221. End Enum
  222. ''' <summary>
  223. ''' 显示弹窗,返回点击按钮的编号(从 1 开始)。
  224. ''' </summary>
  225. ''' <param name="Title">弹窗的标题。</param>
  226. ''' <param name="Caption">弹窗的内容。</param>
  227. ''' <param name="Button1">显示的第一个按钮,默认为“确定”。</param>
  228. ''' <param name="Button2">显示的第二个按钮,默认为空。</param>
  229. ''' <param name="Button3">显示的第三个按钮,默认为空。</param>
  230. ''' <param name="Button1Action">点击第一个按钮将执行该方法,不关闭弹窗。</param>
  231. ''' <param name="Button2Action">点击第二个按钮将执行该方法,不关闭弹窗。</param>
  232. ''' <param name="Button3Action">点击第三个按钮将执行该方法,不关闭弹窗。</param>
  233. ''' <param name="IsWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
  234. Public Function MyMsgBox(Caption As String, Optional Title As String = "提示",
  235. Optional Button1 As String = "确定", Optional Button2 As String = "", Optional Button3 As String = "",
  236. Optional IsWarn As Boolean = False, Optional HighLight As Boolean = True, Optional ForceWait As Boolean = False,
  237. Optional Button1Action As Action = Nothing, Optional Button2Action As Action = Nothing, Optional Button3Action As Action = Nothing) As Integer
  238. '将弹窗列入队列
  239. 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}
  240. WaitingMyMsgBox.Add(Converter)
  241. If RunInUi() Then
  242. '若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗
  243. MyMsgBoxTick()
  244. End If
  245. If Button2.Length > 0 OrElse ForceWait Then
  246. '若有多个按钮则开始等待
  247. If FrmMain Is Nothing OrElse FrmMain.PanMsg Is Nothing AndAlso RunInUi() Then
  248. '主窗体尚未加载,用老土的弹窗来替代
  249. WaitingMyMsgBox.Remove(Converter)
  250. If Button2.Length > 0 Then
  251. Dim RawResult As MsgBoxResult = MsgBox(Caption, If(Button3.Length > 0, MsgBoxStyle.YesNoCancel, MsgBoxStyle.YesNo) + If(IsWarn, MsgBoxStyle.Critical, MsgBoxStyle.Question), Title)
  252. Select Case RawResult
  253. Case MsgBoxResult.Yes
  254. Converter.Result = 1
  255. Case MsgBoxResult.No
  256. Converter.Result = 2
  257. Case MsgBoxResult.Cancel
  258. Converter.Result = 3
  259. End Select
  260. Else
  261. MsgBox(Caption, MsgBoxStyle.OkOnly + If(IsWarn, MsgBoxStyle.Critical, MsgBoxStyle.Question), Title)
  262. Converter.Result = 1
  263. End If
  264. Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" & Button1 & "," & Button2 & "," & Button3, LogLevel.Debug)
  265. Else
  266. Try
  267. FrmMain.DragStop()
  268. ComponentDispatcher.PushModal()
  269. Dispatcher.PushFrame(Converter.WaitFrame)
  270. Finally
  271. ComponentDispatcher.PopModal()
  272. End Try
  273. End If
  274. Log("[Control] 普通弹框返回:" & If(Converter.Result, "null"))
  275. Return Converter.Result
  276. Else
  277. '不进行等待,直接返回
  278. Return 1
  279. End If
  280. End Function
  281. ''' <summary>
  282. ''' 显示输入框并返回输入的文本。若点击第二个按钮,则返回 Nothing。
  283. ''' </summary>
  284. ''' <param name="Title">弹窗的标题。</param>
  285. ''' <param name="ValidateRules">文本框的输入检测。</param>
  286. ''' <param name="Text">弹窗的介绍文本。</param>
  287. ''' <param name="DefaultInput">文本框的默认内容。</param>
  288. ''' <param name="HintText">文本框的提示内容。</param>
  289. ''' <param name="Button1">显示的第一个按钮,默认为“确定”。</param>
  290. ''' <param name="Button2">显示的第二个按钮,默认为“取消”。</param>
  291. ''' <param name="IsWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
  292. 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
  293. '将弹窗列入队列
  294. 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}
  295. WaitingMyMsgBox.Add(Converter)
  296. '虽然我也不知道这是啥但是能用就成了 :)
  297. Try
  298. If FrmMain IsNot Nothing Then FrmMain.DragStop()
  299. ComponentDispatcher.PushModal()
  300. Dispatcher.PushFrame(Converter.WaitFrame)
  301. Finally
  302. ComponentDispatcher.PopModal()
  303. End Try
  304. Log("[Control] 输入弹框返回:" & If(Converter.Result, "null"))
  305. Return Converter.Result
  306. End Function
  307. ''' <summary>
  308. ''' 显示选择框并返回选择的第几项(从 0 开始)。若点击第二个按钮,则返回 Nothing。
  309. ''' </summary>
  310. ''' <param name="Title">弹窗的标题。</param>
  311. ''' <param name="Button1">显示的第一个按钮,默认为 “确定”。</param>
  312. ''' <param name="Button2">显示的第二个按钮,默认为空。</param>
  313. ''' <param name="IsWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
  314. 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?
  315. '将弹窗列入队列
  316. Dim Converter As New MyMsgBoxConverter With {.Type = MyMsgBoxType.Select, .Button1 = Button1, .Button2 = Button2, .Content = Selections, .IsWarn = IsWarn, .Title = Title}
  317. WaitingMyMsgBox.Add(Converter)
  318. '虽然我也不知道这是啥但是能用就成了 :)
  319. Try
  320. If FrmMain IsNot Nothing Then FrmMain.DragStop()
  321. ComponentDispatcher.PushModal()
  322. Dispatcher.PushFrame(Converter.WaitFrame)
  323. Finally
  324. ComponentDispatcher.PopModal()
  325. End Try
  326. Log("[Control] 选择弹框返回:" & If(Converter.Result, "null"))
  327. Return Converter.Result
  328. End Function
  329. ''' <summary>
  330. ''' 等待显示的弹窗。
  331. ''' </summary>
  332. Public WaitingMyMsgBox As List(Of MyMsgBoxConverter) = If(WaitingMyMsgBox, New List(Of MyMsgBoxConverter))
  333. Public Sub MyMsgBoxTick()
  334. Try
  335. If FrmMain Is Nothing OrElse FrmMain.PanMsg Is Nothing OrElse FrmMain.WindowState = WindowState.Minimized Then Exit Sub
  336. If FrmMain.PanMsg.Children.Count > 0 Then
  337. '弹窗中
  338. FrmMain.PanMsg.Visibility = Visibility.Visible
  339. ElseIf WaitingMyMsgBox.Any Then
  340. '没有弹窗,显示一个等待的弹窗
  341. FrmMain.PanMsg.Visibility = Visibility.Visible
  342. Select Case CType(WaitingMyMsgBox(0), MyMsgBoxConverter).Type
  343. Case MyMsgBoxType.Input
  344. FrmMain.PanMsg.Children.Add(New MyMsgInput(WaitingMyMsgBox(0)))
  345. Case MyMsgBoxType.Select
  346. FrmMain.PanMsg.Children.Add(New MyMsgSelect(WaitingMyMsgBox(0)))
  347. Case MyMsgBoxType.Text
  348. FrmMain.PanMsg.Children.Add(New MyMsgText(WaitingMyMsgBox(0)))
  349. Case MyMsgBoxType.Login
  350. FrmMain.PanMsg.Children.Add(New MyMsgLogin(WaitingMyMsgBox(0)))
  351. End Select
  352. WaitingMyMsgBox.RemoveAt(0)
  353. Else
  354. '没有弹窗,没有等待的弹窗
  355. If Not FrmMain.PanMsg.Visibility = Visibility.Collapsed Then FrmMain.PanMsg.Visibility = Visibility.Collapsed
  356. End If
  357. Catch ex As Exception
  358. Log(ex, "处理等待中的弹窗失败", LogLevel.Feedback)
  359. End Try
  360. End Sub
  361. #End Region
  362. #Region "页面声明"
  363. '在最后进行页面声明,避免颜色尚未加载完毕
  364. '窗体声明
  365. Public FrmMain As FormMain
  366. Public FrmStart As SplashScreen
  367. '页面声明(出于单元测试考虑,初始化页面已转入 FormMain 中)
  368. Public FrmLaunchLeft As PageLaunchLeft
  369. Public FrmLaunchRight As PageLaunchRight
  370. Public FrmSelectLeft As PageSelectLeft
  371. Public FrmSelectRight As PageSelectRight
  372. Public FrmSpeedLeft As PageSpeedLeft
  373. Public FrmSpeedRight As PageSpeedRight
  374. '联机页面声明
  375. Public FrmLinkLeft As PageLinkLeft
  376. Public FrmLinkIoi As PageLinkIoi
  377. Public FrmLinkHiper As PageLinkHiper
  378. Public FrmLinkHelp As PageOtherHelpDetail
  379. Public FrmLinkFeedback As PageLinkFeedback
  380. '下载页面声明
  381. Public FrmDownloadLeft As PageDownloadLeft
  382. Public FrmDownloadInstall As PageDownloadInstall
  383. Public FrmDownloadClient As PageDownloadClient
  384. Public FrmDownloadOptiFine As PageDownloadOptiFine
  385. Public FrmDownloadLiteLoader As PageDownloadLiteLoader
  386. Public FrmDownloadForge As PageDownloadForge
  387. Public FrmDownloadNeoForge As PageDownloadNeoForge
  388. Public FrmDownloadFabric As PageDownloadFabric
  389. Public FrmDownloadMod As PageDownloadMod
  390. Public FrmDownloadPack As PageDownloadPack
  391. '设置页面声明
  392. Public FrmSetupLeft As PageSetupLeft
  393. Public FrmSetupLaunch As PageSetupLaunch
  394. Public FrmSetupUI As PageSetupUI
  395. Public FrmSetupSystem As PageSetupSystem
  396. Public FrmSetupLink As PageSetupLink
  397. '其他页面声明
  398. Public FrmOtherLeft As PageOtherLeft
  399. Public FrmOtherHelp As PageOtherHelp
  400. Public FrmOtherAbout As PageOtherAbout
  401. Public FrmOtherTest As PageOtherTest
  402. '登录页面声明
  403. Public FrmLoginLegacy As PageLoginLegacy
  404. Public FrmLoginNide As PageLoginNide
  405. Public FrmLoginNideSkin As PageLoginNideSkin
  406. Public FrmLoginAuth As PageLoginAuth
  407. Public FrmLoginAuthSkin As PageLoginAuthSkin
  408. Public FrmLoginMs As PageLoginMs
  409. Public FrmLoginMsSkin As PageLoginMsSkin
  410. '版本设置页面声明
  411. Public FrmVersionLeft As PageVersionLeft
  412. Public FrmVersionOverall As PageVersionOverall
  413. Public FrmVersionMod As PageVersionMod
  414. Public FrmVersionModDisabled As PageVersionModDisabled
  415. Public FrmVersionSetup As PageVersionSetup
  416. '资源信息分页声明
  417. Public FrmDownloadCompDetail As PageDownloadCompDetail
  418. #End Region
  419. #Region "帮助"
  420. Public Class HelpEntry
  421. ''' <summary>
  422. ''' 原始信息路径。用于刷新。
  423. ''' </summary>
  424. Public RawPath As String
  425. '基础
  426. ''' <summary>
  427. ''' 显示标题。
  428. ''' </summary>
  429. Public Title As String
  430. ''' <summary>
  431. ''' 显示描述。
  432. ''' </summary>
  433. Public Desc As String
  434. ''' <summary>
  435. ''' 检索关键字。
  436. ''' </summary>
  437. Public Search As String
  438. ''' <summary>
  439. ''' 用于分类的标签列表。
  440. ''' </summary>
  441. Public Types As List(Of String)
  442. '显示(可选)
  443. ''' <summary>
  444. ''' 帮助项的自定义图标。可能为 Nothing。
  445. ''' </summary>
  446. Public Logo As String = Nothing
  447. ''' <summary>
  448. ''' 是否显示在搜索结果。默认为 True。
  449. ''' </summary>
  450. Public ShowInSearch As Boolean = True
  451. ''' <summary>
  452. ''' 是否在公开版的 PCL 中显示(这会影响主页与搜索)。默认为 True。
  453. ''' </summary>
  454. Public ShowInPublic As Boolean = True
  455. ''' <summary>
  456. ''' 是否在快照版的 PCL 中显示(这会影响主页与搜索)。默认为 True。
  457. ''' </summary>
  458. Public ShowInSnapshot As Boolean = True
  459. '动作
  460. ''' <summary>
  461. ''' 是否为 “执行事件”。
  462. ''' </summary>
  463. Public IsEvent As Boolean
  464. Public EventType As String
  465. Public EventData As String
  466. ''' <summary>
  467. ''' 若非执行事件,其对应的 .xaml 本地文件内容。
  468. ''' </summary>
  469. Public XamlContent As String
  470. '转换
  471. ''' <summary>
  472. ''' 从文件初始化 HelpEntry 对象,失败会抛出异常。
  473. ''' </summary>
  474. Public Sub New(FilePath As String)
  475. RawPath = FilePath
  476. Dim JsonData As JObject = GetJson(HelpArgumentReplace(ReadFile(FilePath)))
  477. If JsonData Is Nothing Then Throw New FileNotFoundException("未找到帮助文件:" & FilePath, FilePath)
  478. '加载常规信息
  479. If JsonData("Title") IsNot Nothing Then
  480. Title = JsonData("Title")
  481. Else
  482. Throw New ArgumentException("未找到 Title 项")
  483. End If
  484. Desc = If(JsonData("Description"), "")
  485. Search = If(JsonData("Keywords"), "")
  486. Logo = JsonData("Logo") '为保持 Nothing,不要加 If
  487. ShowInSearch = If(JsonData("ShowInSearch"), ShowInSearch)
  488. ShowInPublic = If(JsonData("ShowInPublic"), ShowInPublic)
  489. ShowInSnapshot = If(JsonData("ShowInSnapshot"), ShowInSnapshot)
  490. Types = New List(Of String)
  491. For Each NameOfType In If(JsonData("Types"), GetJson("[]"))
  492. Types.Add(NameOfType)
  493. Next
  494. '加载事件信息
  495. If If(JsonData("IsEvent"), False) Then
  496. EventType = JsonData("EventType")
  497. If EventType Is Nothing Then Throw New ArgumentException("未找到 EventType 项")
  498. EventData = If(JsonData("EventData"), "")
  499. IsEvent = True
  500. Else
  501. Dim XamlAddress As String = FilePath.ToLower.Replace(".json", ".xaml")
  502. If File.Exists(XamlAddress) Then
  503. XamlContent = ReadFile(XamlAddress)
  504. IsEvent = False
  505. Else
  506. Throw New FileNotFoundException("未找到帮助条目 .json 对应的 .xaml 文件(" & XamlAddress & ")")
  507. End If
  508. End If
  509. End Sub
  510. ''' <summary>
  511. ''' 获取该 HelpEntry 对应的 MyListItem。
  512. ''' </summary>
  513. Public Function ToListItem() As MyListItem
  514. Return SetToListItem(New MyListItem)
  515. End Function
  516. ''' <summary>
  517. ''' 将属性设置入一个现有的 ListItem。
  518. ''' </summary>
  519. Public Function SetToListItem(Item As MyListItem) As MyListItem
  520. Dim Logo As String
  521. If IsEvent Then
  522. If EventType = "弹出窗口" Then
  523. Logo = PathImage & "Blocks/GrassPath.png"
  524. Else
  525. Logo = PathImage & "Blocks/CommandBlock.png"
  526. End If
  527. Else
  528. Logo = PathImage & "Blocks/Grass.png"
  529. End If
  530. '设置属性
  531. With Item
  532. .SnapsToDevicePixels = True
  533. .Title = Title
  534. .Info = Desc
  535. .Logo = If(Me.Logo, Logo)
  536. .Height = 42
  537. .Type = MyListItem.CheckType.Clickable
  538. .Tag = Me
  539. .EventType = Nothing
  540. .EventData = Nothing
  541. End With
  542. '项目的点击事件
  543. AddHandler Item.Click, Sub(sender, e) PageOtherHelp.OnItemClick(sender.Tag)
  544. Return Item
  545. End Function
  546. End Class
  547. Public HelpLoader As New LoaderTask(Of Integer, List(Of HelpEntry))("Help Page", AddressOf HelpLoad,, ThreadPriority.BelowNormal)
  548. Private ReadOnly HelpLoadLock As New Object
  549. ''' <summary>
  550. ''' 初始化帮助列表对象。
  551. ''' </summary>
  552. Private Sub HelpLoad(Loader As LoaderTask(Of Integer, List(Of HelpEntry)))
  553. SyncLock HelpLoadLock '避免重复解压文件导致出错
  554. Try
  555. '解压内置文件
  556. HelpTryExtract()
  557. '遍历文件
  558. Dim FileList As New List(Of String)
  559. Try
  560. Dim IgnoreList As New List(Of String)
  561. '读取自定义文件
  562. If Directory.Exists(Path & "PCL\Help\") Then
  563. For Each File In EnumerateFiles(Path & "PCL\Help\")
  564. Select Case File.Extension.ToLower
  565. Case ".helpignore"
  566. '加载忽略列表
  567. Log("[Help] 发现 .helpignore 文件:" & File.FullName)
  568. For Each Line In ReadFile(File.FullName).Split(vbCrLf.ToCharArray)
  569. Dim RealString As String = Line.BeforeFirst("#").Trim
  570. If String.IsNullOrWhiteSpace(RealString) Then Continue For
  571. IgnoreList.Add(RealString)
  572. If ModeDebug Then Log("[Help] > " & RealString)
  573. Next
  574. Case ".json"
  575. FileList.Add(File.FullName)
  576. End Select
  577. Next
  578. End If
  579. Log("[Help] 已扫描 PCL 文件夹下的帮助文件,目前总计 " & FileList.Count & " 条")
  580. '读取自带文件
  581. For Each File In EnumerateFiles(PathTemp & "Help")
  582. '跳过非 json 文件与以 . 开头的文件夹
  583. If File.Extension.ToLower <> ".json" OrElse File.Directory.FullName.Replace(PathTemp & "Help", "").Contains("\.") Then Continue For
  584. '检查忽略列表
  585. Dim RealPath As String = File.FullName.Replace(PathTemp & "Help\", "")
  586. For Each Ignore In IgnoreList
  587. If RegexCheck(RealPath, Ignore) Then
  588. If ModeDebug Then Log("[Help] 已忽略 " & RealPath & ":" & Ignore)
  589. GoTo NextFile
  590. End If
  591. Next
  592. FileList.Add(File.FullName)
  593. NextFile:
  594. Next
  595. Log("[Help] 已扫描缓存文件夹下的帮助文件,目前总计 " & FileList.Count & " 条")
  596. Catch ex As Exception
  597. Log(ex, "检查帮助文件夹失败", LogLevel.Msgbox)
  598. End Try
  599. If Loader.IsAborted Then Exit Sub
  600. '将文件实例化
  601. Dim Dict As New List(Of HelpEntry)
  602. For Each FilePath As String In FileList
  603. Try
  604. Dim Entry As New HelpEntry(FilePath)
  605. Dict.Add(Entry)
  606. If ModeDebug Then Log("[Help] 已加载的帮助条目:" & Entry.Title & " ← " & FilePath)
  607. Catch ex As Exception
  608. Log(ex, "初始化帮助条目失败(" & FilePath & ")", LogLevel.Msgbox)
  609. End Try
  610. Next
  611. '回设
  612. If Not Dict.Any() Then Throw New Exception("未找到可用的帮助;若不需要帮助页面,可以在 设置 → 个性化 → 功能隐藏 中将其隐藏")
  613. If Loader.IsAborted Then Exit Sub
  614. Loader.Output = Dict
  615. Catch ex As Exception
  616. Log(ex, "帮助列表初始化失败")
  617. Throw
  618. End Try
  619. End SyncLock
  620. End Sub
  621. ''' <summary>
  622. ''' 尝试解压内置帮助文件。
  623. ''' </summary>
  624. Public Sub HelpTryExtract()
  625. If Setup.Get("SystemHelpVersion") <> VersionCode OrElse Not File.Exists(PathTemp & "Help\启动器\备份设置.xaml") Then
  626. DeleteDirectory(PathTemp & "Help")
  627. Directory.CreateDirectory(PathTemp & "Help")
  628. WriteFile(PathTemp & "Cache\Help.zip", GetResources("Help"))
  629. ExtractFile(PathTemp & "Cache\Help.zip", PathTemp & "Help", Encoding.UTF8)
  630. Setup.Set("SystemHelpVersion", VersionCode)
  631. Log("[Help] 已解压内置帮助文件,目前状态:" & File.Exists(PathTemp & "Help\启动器\备份设置.xaml"), LogLevel.Debug)
  632. End If
  633. End Sub
  634. ''' <summary>
  635. ''' 对帮助文件约定的替换标记进行处理,如果遇到需要转义的字符会进行转义。
  636. ''' </summary>
  637. Public Function HelpArgumentReplace(Xaml As String) As String
  638. Dim Result = Xaml.Replace("{path}", EscapeXML(Path))
  639. Result = RegexReplaceEach(Result, Function() EscapeXML(PageOtherTest.GetRandomHint()), "\{hint\}")
  640. Result = RegexReplaceEach(Result, Function() EscapeXML(PageOtherTest.GetRandomCave()), "\{cave\}")
  641. Return Result
  642. End Function
  643. #End Region
  644. #Region "愚人节"
  645. Public IsAprilEnabled As Boolean = Date.Now.Month = 4 AndAlso Date.Now.Day = 1
  646. Public IsAprilGiveup As Boolean = False
  647. Private AprilSpeed As New Vector(0, 0)
  648. Private AprilIdieCount As Integer = 0, AprilMousePosLast As New Point(0, 0)
  649. Private AprilDistance As Integer = 0
  650. Private Sub TimerFool()
  651. Try
  652. If FrmLaunchLeft Is Nothing OrElse FrmLaunchLeft.AprilPosTrans Is Nothing OrElse FrmMain.lastMouseArg Is Nothing Then Exit Sub
  653. If IsAprilGiveup OrElse FrmMain.PageCurrent <> FormMain.PageType.Launch OrElse AniControlEnabled <> 0 OrElse Not FrmLaunchLeft.BtnLaunch.IsLoaded Then Exit Sub
  654. '计算是否空闲
  655. Dim MousePos = FrmMain.lastMouseArg.GetPosition(FrmMain)
  656. If MousePos = AprilMousePosLast Then
  657. AprilIdieCount += 1
  658. Else
  659. AprilMousePosLast = MousePos
  660. AprilIdieCount = 0
  661. End If
  662. '计算躲避移动
  663. Dim Direction As Vector
  664. Dim Distance As Double
  665. Dim ButtonWidth = FrmLaunchLeft.BtnLaunch.ActualWidth / 2, ButtonHeight = FrmLaunchLeft.BtnLaunch.ActualHeight / 2
  666. Dim Vec As Vector = FrmMain.lastMouseArg.GetPosition(FrmLaunchLeft.BtnLaunch) - New Vector(ButtonWidth, ButtonHeight)
  667. Dim Dir As New Vector(Vec.X, Vec.Y)
  668. Dir.Normalize()
  669. Direction = -Dir
  670. Distance = New Vector(Math.Max(0, Math.Abs(Vec.X) - ButtonWidth), Math.Max(0, Math.Abs(Vec.Y) - ButtonHeight)).Length
  671. Dim BreathScale = Math.Sin(Timer150Count / 37.5 * Math.PI)
  672. Dim Acc = Math.Max(0, BreathScale * 0.25 - 0.65 - Math.Log((Distance + 0.4) / 200)) * Direction '加速度
  673. '计算回归移动
  674. If AprilIdieCount >= 64 * 5 Then
  675. Dim SafeDist As Vector = FrmMain.lastMouseArg.GetPosition(FrmMain.PanMain) - New Vector(ButtonWidth, FrmMain.PanMain.ActualHeight - ButtonHeight * 3)
  676. Dim Back As New Vector(FrmLaunchLeft.AprilPosTrans.X, FrmLaunchLeft.AprilPosTrans.Y)
  677. If SafeDist.Length > 250 AndAlso Back.Length > 0.4 Then
  678. Acc -= Back * 0.0005
  679. Back.Normalize()
  680. Acc -= Back * 0.15
  681. End If
  682. End If
  683. '回到边界
  684. Dim Relative As Point = FrmLaunchLeft.BtnLaunch.TranslatePoint(New Point(0, 0), FrmMain.PanForm)
  685. If Relative.X < -ButtonWidth * 2 Then
  686. FrmLaunchLeft.AprilPosTrans.X += FrmMain.PanForm.ActualWidth + ButtonWidth * 2 '离开左边界
  687. AprilSpeed.X -= 80
  688. If Relative.Y < 0 Then
  689. FrmLaunchLeft.AprilPosTrans.Y += ButtonHeight * 2.5
  690. ElseIf Relative.Y > FrmMain.PanForm.ActualHeight - ButtonHeight * 2 Then
  691. FrmLaunchLeft.AprilPosTrans.Y -= ButtonHeight * 2.5
  692. End If
  693. ElseIf Relative.X > FrmMain.PanForm.ActualWidth Then
  694. FrmLaunchLeft.AprilPosTrans.X -= FrmMain.PanForm.ActualWidth + ButtonWidth * 2 '离开右边界
  695. AprilSpeed.X += 80
  696. If Relative.Y < 0 Then
  697. FrmLaunchLeft.AprilPosTrans.Y += ButtonHeight * 2.5
  698. ElseIf Relative.Y > FrmMain.PanForm.ActualHeight - ButtonHeight * 2 Then
  699. FrmLaunchLeft.AprilPosTrans.Y -= ButtonHeight * 2.5
  700. End If
  701. ElseIf Relative.Y < -ButtonHeight * 2 Then
  702. FrmLaunchLeft.AprilPosTrans.Y += FrmMain.PanForm.ActualHeight + ButtonHeight * 2 '离开上边界
  703. AprilSpeed.Y -= 25
  704. If Relative.X < 0 Then
  705. FrmLaunchLeft.AprilPosTrans.X += ButtonWidth * 2
  706. ElseIf Relative.X > FrmMain.PanForm.ActualWidth - ButtonWidth * 2 Then
  707. FrmLaunchLeft.AprilPosTrans.X -= ButtonWidth * 2
  708. End If
  709. ElseIf Relative.Y > FrmMain.PanForm.ActualHeight Then
  710. FrmLaunchLeft.AprilPosTrans.Y -= FrmMain.PanForm.ActualHeight + ButtonHeight * 2 '离开下边界
  711. AprilSpeed.Y += 25
  712. If Relative.X < 0 Then
  713. FrmLaunchLeft.AprilPosTrans.X += ButtonWidth * 2
  714. ElseIf Relative.X > FrmMain.PanForm.ActualWidth - ButtonWidth * 2 Then
  715. FrmLaunchLeft.AprilPosTrans.X -= ButtonWidth * 2
  716. End If
  717. End If
  718. '移动
  719. AprilSpeed = AprilSpeed * 0.8 + Acc
  720. Dim SpeedValue = Math.Min(60, AprilSpeed.Length)
  721. If SpeedValue < 0.01 Then Exit Sub
  722. AprilSpeed.Normalize()
  723. AprilSpeed *= SpeedValue
  724. AprilDistance += SpeedValue
  725. FrmLaunchLeft.AprilPosTrans.X += AprilSpeed.X
  726. FrmLaunchLeft.AprilPosTrans.Y += AprilSpeed.Y
  727. '大小改变
  728. FrmLaunchLeft.AprilScaleTrans.ScaleX = MathClamp(1 - (Math.Abs(Direction.X) - Math.Abs(Direction.Y)) * (SpeedValue / 160), 0.2, 1.8)
  729. FrmLaunchLeft.AprilScaleTrans.ScaleY = MathClamp(1 - (Math.Abs(Direction.Y) - Math.Abs(Direction.X)) * (SpeedValue / 100), 0.2, 1.8)
  730. '放弃提示
  731. If AprilDistance > 4000 Then
  732. AprilDistance = -4000
  733. Select Case RandomInteger(0, 3)
  734. Case 0
  735. Hint("放弃吧!只需要点一下右下角的小白旗……")
  736. Case 1
  737. Hint("看到右下角的那面小白旗了吗?")
  738. Case 2
  739. Hint("这里建议点一下右下角的小白旗投降呢.jpg")
  740. Case 3
  741. Hint("右下角的小白旗永远等着你……")
  742. End Select
  743. End If
  744. Catch ex As Exception
  745. Log(ex, "愚人节移动出错", LogLevel.Feedback)
  746. End Try
  747. End Sub
  748. #End Region
  749. #Region "系统"
  750. ''' <summary>
  751. ''' 把某个 PCL 窗口拖到最前面。
  752. ''' </summary>
  753. Public Sub ShowWindowToTop(Handle As IntPtr)
  754. Try
  755. PostMessage(Handle, 400 * 16 + 2, 0, 0)
  756. SetForegroundWindow(Handle) '不在这里放不行,神秘 WinAPI,建议别动
  757. Catch ex As Exception
  758. Log(ex, "设置窗口置顶失败", LogLevel.Hint)
  759. End Try
  760. End Sub
  761. Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ClassName As String, WindowName As String) As IntPtr
  762. Public Declare Function SetForegroundWindow Lib "user32" (hWnd As IntPtr) As Integer
  763. Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (hWnd As IntPtr, msg As UInteger, wParam As Long, lParam As Long) As Boolean
  764. #End Region
  765. Public DragControl = Nothing
  766. Private Timer4Count As Integer = 0
  767. Private Timer150Count As Integer = 0
  768. Private Sub TimerMain()
  769. Try
  770. #Region "每 50ms 执行一次的代码"
  771. HintTick()
  772. MyMsgBoxTick()
  773. FrmMain.DragTick()
  774. LoaderTaskbarProgressRefresh()
  775. If ThemeDontClick = 2 Then ThemeRefresh()
  776. #End Region
  777. Catch ex As Exception
  778. Log(ex, "短程主时钟执行异常", LogLevel.Assert)
  779. End Try
  780. Timer4Count += 1
  781. If Timer4Count = 4 Then
  782. Timer4Count = 0
  783. Try
  784. #Region "每 250ms 执行一次的代码"
  785. If ThemeNow = 12 Then ThemeRefresh()
  786. #End Region
  787. Catch ex As Exception
  788. Log(ex, "中程主时钟执行异常", LogLevel.Debug)
  789. End Try
  790. End If
  791. Timer150Count += 1
  792. If Timer150Count = 150 Then
  793. Timer150Count = 0
  794. Try
  795. #Region "每 7.5s 执行一次的代码"
  796. If FrmMain.BtnExtraApril_ShowCheck AndAlso AprilDistance <> 0 Then FrmMain.BtnExtraApril.Ribble()
  797. '以未知原因窗口被丢到一边去的修复(Top、Left = -25600),还有 #745
  798. RunInUi(Sub()
  799. If Not FrmMain.Hidden Then
  800. If FrmMain.Top < -9000 Then FrmMain.Top = 100
  801. If FrmMain.Left < -9000 Then FrmMain.Left = 100 '窗口拉至最大时 Left = -18.8
  802. End If
  803. End Sub)
  804. #End Region
  805. Catch ex As Exception
  806. Log(ex, "长程主时钟执行异常", LogLevel.Assert)
  807. End Try
  808. End If
  809. End Sub
  810. Public Sub TimerMainStart()
  811. RunInNewThread(
  812. Sub()
  813. Try
  814. Do While True
  815. RunInUiWait(AddressOf TimerMain)
  816. Thread.Sleep(50 * 0.98)
  817. Loop
  818. Catch ex As Exception
  819. Log(ex, "程序主时钟出错", LogLevel.Feedback)
  820. End Try
  821. End Sub, "Timer Main")
  822. If Not IsAprilEnabled Then Exit Sub
  823. RunInNewThread(
  824. Sub()
  825. Try
  826. Dim LastTime = My.Computer.Clock.TickCount
  827. Do While True
  828. If LastTime <> My.Computer.Clock.TickCount Then
  829. LastTime = My.Computer.Clock.TickCount
  830. RunInUiWait(AddressOf TimerFool)
  831. End If
  832. Thread.Sleep(1)
  833. Loop
  834. Catch ex As Exception
  835. Log(ex, "愚人节主时钟出错", LogLevel.Feedback)
  836. End Try
  837. End Sub, "Timer Main Fool")
  838. End Sub
  839. End Module