MyLocalModItem.xaml.vb 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. Imports System.Windows.Forms
  2. Public Class MyLocalModItem
  3. #Region "基础属性"
  4. Public Uuid As Integer = GetUuid()
  5. 'Logo
  6. Private _Logo As String = ""
  7. Public Property Logo As String
  8. Get
  9. Return _Logo
  10. End Get
  11. Set(value As String)
  12. If _Logo = value OrElse value Is Nothing Then Exit Property
  13. _Logo = value
  14. Dim FileAddress = PathTemp & "CompLogo\" & GetHash(_Logo) & ".png"
  15. Try
  16. If _Logo.ToLower.StartsWith("http") Then
  17. '网络图片
  18. If File.Exists(FileAddress) Then
  19. PathLogo.Source = New MyBitmap(FileAddress)
  20. ElseIf _Logo.ToLower.EndsWith(".webp") Then 'Modrinth 林业 Mod 使用了不支持的 WebP 格式 Logo
  21. Log($"[LocalModItem] 发现不支持的 WebP 格式图标,已更改为默认图标:{_Logo}")
  22. PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png")
  23. Else
  24. PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png")
  25. RunInNewThread(Sub() LogoLoader(FileAddress), "Comp Logo Loader " & Uuid & "#", ThreadPriority.BelowNormal)
  26. End If
  27. Else
  28. '位图
  29. PathLogo.Source = New MyBitmap(_Logo)
  30. End If
  31. Catch ex As IOException
  32. Log(ex, "加载本地 Mod 图标时读取失败(" & FileAddress & ")")
  33. Catch ex As ArgumentException
  34. '考虑缓存的图片本身可能有误
  35. Log(ex, "可视化本地 Mod 图标失败(" & FileAddress & ")")
  36. Try
  37. File.Delete(FileAddress)
  38. Log("[LocalModItem] 已清理损坏的本地 Mod 图标:" & FileAddress)
  39. Catch exx As Exception
  40. Log(exx, "清理损坏的本地 Mod 图标缓存失败(" & FileAddress & ")", LogLevel.Hint)
  41. End Try
  42. Catch ex As Exception
  43. Log(ex, "加载本地 Mod 图标失败(" & value & ")")
  44. End Try
  45. End Set
  46. End Property
  47. '后台加载 Logo
  48. Private Sub LogoLoader(LocalFileAddress As String)
  49. Dim Retried As Boolean = False
  50. Dim DownloadEnd As String = GetUuid()
  51. RetryStart:
  52. Try
  53. 'CurseForge 图片使用缩略图
  54. Dim Url As String = _Logo
  55. If Url.Contains("/256/256/") AndAlso GetPixelSize(1) <= 1.25 AndAlso Not Retried Then
  56. Url = Url.Replace("/256/256/", "/64/64/") '#3075:部分 Mod 不存在 64x64 图标,所以重试时不再缩小
  57. End If
  58. '下载图片
  59. NetDownload(Url, LocalFileAddress & DownloadEnd)
  60. Dim LoadError As Exception = Nothing
  61. RunInUiWait(
  62. Sub()
  63. Try
  64. '在地址更换时取消加载
  65. If LocalFileAddress <> $"{PathTemp}CompLogo\{GetHash(_Logo)}.png" Then Exit Sub
  66. '在完成正常加载后才保存缓存图片
  67. PathLogo.Source = New MyBitmap(LocalFileAddress & DownloadEnd)
  68. Catch ex As Exception
  69. Log(ex, "读取本地 Mod 图标失败(" & LocalFileAddress & ")")
  70. File.Delete(LocalFileAddress & DownloadEnd)
  71. LoadError = ex
  72. End Try
  73. End Sub)
  74. If LoadError IsNot Nothing Then Throw LoadError
  75. If File.Exists(LocalFileAddress) Then
  76. File.Delete(LocalFileAddress & DownloadEnd)
  77. Else
  78. FileIO.FileSystem.MoveFile(LocalFileAddress & DownloadEnd, LocalFileAddress)
  79. End If
  80. Catch ex As Exception
  81. If Not Retried Then
  82. Retried = True
  83. GoTo RetryStart
  84. Else
  85. Log(ex, $"下载本地 Mod 图标失败({_Logo})")
  86. RunInUi(Sub() PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png"))
  87. End If
  88. End Try
  89. End Sub
  90. '标题
  91. Private _Title As String
  92. Public Property Title As String
  93. Get
  94. Return _Title
  95. End Get
  96. Set(value As String)
  97. Dim RawValue = value
  98. Select Case Entry.State
  99. Case McMod.McModState.Fine
  100. LabTitle.TextDecorations = Nothing
  101. Case McMod.McModState.Disabled
  102. LabTitle.TextDecorations = TextDecorations.Strikethrough
  103. Case McMod.McModState.Unavaliable
  104. LabTitle.TextDecorations = TextDecorations.Strikethrough
  105. value &= " [错误]"
  106. End Select
  107. If LabTitle.Text = value Then Exit Property
  108. LabTitle.Text = value
  109. _Title = RawValue
  110. End Set
  111. End Property
  112. '副标题
  113. Public Property SubTitle As String
  114. Get
  115. Return If(LabTitleRaw?.Text, "")
  116. End Get
  117. Set(value As String)
  118. If LabTitleRaw.Text = value Then Exit Property
  119. LabTitleRaw.Text = value
  120. LabTitleRaw.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
  121. End Set
  122. End Property
  123. '描述
  124. Public Property Description As String
  125. Get
  126. Return LabInfo.Text
  127. End Get
  128. Set(value As String)
  129. If LabInfo.Text = value Then Exit Property
  130. LabInfo.Text = value
  131. End Set
  132. End Property
  133. 'Tag
  134. Public WriteOnly Property Tags As List(Of String)
  135. Set(value As List(Of String))
  136. PanTags.Children.Clear()
  137. PanTags.Visibility = If(value.Any(), Visibility.Visible, Visibility.Collapsed)
  138. For Each TagText In value
  139. Dim NewTag = GetObjectFromXML(
  140. "<Border xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
  141. Background=""#11000000"" Padding=""3,1"" CornerRadius=""3"" Margin=""0,0,3,0""
  142. SnapsToDevicePixels=""True"" UseLayoutRounding=""False"">
  143. <TextBlock Text=""" & TagText & """ Foreground=""#868686"" FontSize=""11"" />
  144. </Border>")
  145. PanTags.Children.Add(NewTag)
  146. Next
  147. End Set
  148. End Property
  149. '相关联的 Mod
  150. Public Property Entry As McMod
  151. Get
  152. Return Tag
  153. End Get
  154. Set(value As McMod)
  155. Tag = value
  156. End Set
  157. End Property
  158. #End Region
  159. #Region "点击与勾选"
  160. '触发点击事件
  161. Public Event Click(sender As Object, e As MouseButtonEventArgs)
  162. Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
  163. If IsMouseDown Then
  164. RaiseEvent Click(sender, e)
  165. If e.Handled Then Exit Sub
  166. Log("[Control] 按下本地 Mod 列表项:" & LabTitle.Text)
  167. End If
  168. End Sub
  169. '鼠标点击判定
  170. Private IsMouseDown As Boolean = False
  171. Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonDown
  172. If Not IsMouseDirectlyOver Then Exit Sub
  173. IsMouseDown = True
  174. If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = False
  175. End Sub
  176. Private Sub Button_MouseLeave(sender As Object, e As Object) Handles Me.MouseLeave, Me.PreviewMouseLeftButtonUp
  177. IsMouseDown = False
  178. If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True
  179. End Sub
  180. '勾选状态
  181. Public Event Check(sender As Object, e As RouteEventArgs)
  182. Public Event Changed(sender As Object, e As RouteEventArgs)
  183. Private _Checked As Boolean = False
  184. Public Property Checked As Boolean
  185. Get
  186. Return _Checked
  187. End Get
  188. Set(value As Boolean)
  189. Try
  190. '触发属性值修改
  191. Dim ChangedEventArgs As New RouteEventArgs(False)
  192. Dim RawValue = _Checked
  193. If value = _Checked Then Exit Property
  194. _Checked = value
  195. If IsInitialized Then
  196. RaiseEvent Changed(Me, ChangedEventArgs)
  197. If ChangedEventArgs.Handled Then
  198. _Checked = RawValue
  199. Exit Property
  200. End If
  201. End If
  202. If value Then
  203. Dim CheckEventArgs As New RouteEventArgs(False)
  204. RaiseEvent Check(Me, CheckEventArgs)
  205. If CheckEventArgs.Handled Then Exit Property
  206. End If
  207. '更改动画
  208. If IsVisibleInForm() Then
  209. Dim Anim As New List(Of AniData)
  210. If Checked Then
  211. '由无变有
  212. Dim Delta = 32 - RectCheck.ActualHeight
  213. Anim.Add(AaHeight(RectCheck, Delta * 0.4, 200,, New AniEaseOutFluent(AniEasePower.Weak)))
  214. Anim.Add(AaHeight(RectCheck, Delta * 0.6, 300,, New AniEaseOutBack(AniEasePower.Weak)))
  215. Anim.Add(AaOpacity(RectCheck, 1 - RectCheck.Opacity, 30))
  216. RectCheck.VerticalAlignment = VerticalAlignment.Center
  217. RectCheck.Margin = New Thickness(-3, 0, 0, 0)
  218. Anim.Add(AaColor(LabTitle, TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush2", "ColorBrush5"), 200))
  219. Else
  220. '由有变无
  221. Anim.Add(AaHeight(RectCheck, -RectCheck.ActualHeight, 120,, New AniEaseInFluent(AniEasePower.Weak)))
  222. Anim.Add(AaOpacity(RectCheck, -RectCheck.Opacity, 70, 40))
  223. RectCheck.VerticalAlignment = VerticalAlignment.Center
  224. Anim.Add(AaColor(LabTitle, TextBlock.ForegroundProperty, If(LabTitle.TextDecorations Is Nothing, "ColorBrush1", "ColorBrushGray4"), 120))
  225. End If
  226. AniStart(Anim, "MyLocalModItem Checked " & Uuid)
  227. Else
  228. '不在窗口上时直接设置
  229. RectCheck.VerticalAlignment = VerticalAlignment.Center
  230. RectCheck.Margin = New Thickness(-3, 0, 0, 0)
  231. If Checked Then
  232. RectCheck.Height = 32
  233. RectCheck.Opacity = 1
  234. LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush2", "ColorBrush5"))
  235. Else
  236. RectCheck.Height = 0
  237. RectCheck.Opacity = 0
  238. LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush1", "ColorBrushGray4"))
  239. End If
  240. AniStop("MyLocalModItem Checked " & Uuid)
  241. End If
  242. Catch ex As Exception
  243. Log(ex, "设置 Checked 失败")
  244. End Try
  245. End Set
  246. End Property
  247. #End Region
  248. #Region "后加载内容"
  249. '右下角状态指示图标
  250. Private ImgState As Image
  251. '指向背景
  252. Private _RectBack As Border = Nothing
  253. Public ReadOnly Property RectBack As Border
  254. Get
  255. If _RectBack Is Nothing Then
  256. Dim Rect As New Border With {
  257. .Name = "RectBack",
  258. .CornerRadius = New CornerRadius(3),
  259. .RenderTransform = New ScaleTransform(0.8, 0.8),
  260. .RenderTransformOrigin = New Point(0.5, 0.5),
  261. .BorderThickness = New Thickness(GetWPFSize(1)),
  262. .SnapsToDevicePixels = True,
  263. .IsHitTestVisible = False,
  264. .Opacity = 0
  265. }
  266. Rect.SetResourceReference(Border.BackgroundProperty, "ColorBrush7")
  267. Rect.SetResourceReference(Border.BorderBrushProperty, "ColorBrush6")
  268. SetColumnSpan(Rect, 999)
  269. SetRowSpan(Rect, 999)
  270. Children.Insert(0, Rect)
  271. _RectBack = Rect
  272. '<!--<Border x:Name = "RectBack" CornerRadius="3" RenderTransformOrigin="0.5,0.5" SnapsToDevicePixels="True"
  273. 'IsHitTestVisible = "False" Opacity="0" BorderThickness="1"
  274. 'Grid.ColumnSpan = "4" Background="{DynamicResource ColorBrush7}" BorderBrush="{DynamicResource ColorBrush6}"/>-->
  275. End If
  276. Return _RectBack
  277. End Get
  278. End Property
  279. '按钮
  280. Public ButtonHandler As Action(Of MyLocalModItem, EventArgs)
  281. Public ButtonStack As FrameworkElement
  282. Private _Buttons As IEnumerable(Of MyIconButton)
  283. Public Property Buttons As IEnumerable(Of MyIconButton)
  284. Get
  285. Return _Buttons
  286. End Get
  287. Set(value As IEnumerable(Of MyIconButton))
  288. _Buttons = value
  289. '移除原 Stack
  290. If ButtonStack IsNot Nothing Then
  291. Children.Remove(ButtonStack)
  292. ButtonStack = Nothing
  293. End If
  294. If value.Count = 0 Then Exit Property
  295. '添加新 Stack
  296. ButtonStack = New StackPanel With {.Opacity = 0, .Margin = New Thickness(0, 0, 5, 0), .SnapsToDevicePixels = False, .Orientation = Orientation.Horizontal,
  297. .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Center, .UseLayoutRounding = False}
  298. SetColumnSpan(ButtonStack, 10) : SetRowSpan(ButtonStack, 10)
  299. '构造按钮
  300. For Each Btn As MyIconButton In value
  301. If Btn.Height.Equals(Double.NaN) Then Btn.Height = 25
  302. If Btn.Width.Equals(Double.NaN) Then Btn.Width = 25
  303. CType(ButtonStack, StackPanel).Children.Add(Btn)
  304. Next
  305. Children.Add(ButtonStack)
  306. End Set
  307. End Property
  308. '勾选条
  309. Private _RectCheck As Border
  310. Public ReadOnly Property RectCheck As Border
  311. Get
  312. If _RectCheck Is Nothing Then
  313. _RectCheck = New Border With {.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2),
  314. .VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center),
  315. .HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False,
  316. .Margin = If(Checked, New Thickness(-3, 6, 0, 6), New Thickness(-3, 0, 0, 0))}
  317. _RectCheck.SetResourceReference(Border.BackgroundProperty, "ColorBrush3")
  318. SetRowSpan(_RectCheck, 10)
  319. Children.Add(_RectCheck)
  320. End If
  321. Return _RectCheck
  322. End Get
  323. End Property
  324. #End Region
  325. Public Sub Refresh()
  326. RunInUi(
  327. Sub()
  328. '标题
  329. If Entry.Comp Is Nothing Then
  330. Title = Entry.Name
  331. SubTitle = If(Entry.Version Is Nothing, "", " | " & Entry.Version)
  332. Else
  333. Dim Titles = Entry.Comp.GetControlTitle(False)
  334. Title = Titles.Key
  335. SubTitle = Titles.Value & If(Entry.Version Is Nothing, "", " | " & Entry.Version)
  336. End If
  337. If Checked Then
  338. LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush2", "ColorBrush5"))
  339. Else
  340. LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush1", "ColorBrushGray4"))
  341. End If
  342. '描述
  343. Dim NewDescription As String
  344. Select Case Entry.State
  345. Case McMod.McModState.Fine
  346. NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path)
  347. Case McMod.McModState.Disabled
  348. NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path.Replace(".disabled", "").Replace(".old", ""))
  349. Case Else 'McMod.McModState.Unavaliable
  350. NewDescription = GetFileNameFromPath(Entry.Path)
  351. End Select
  352. If Entry.Comp IsNot Nothing Then
  353. NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "")
  354. ElseIf Entry.Description IsNot Nothing Then
  355. NewDescription += ": " & Entry.Description.Replace(vbCr, "").Replace(vbLf, "")
  356. ElseIf Not Entry.IsFileAvailable Then
  357. NewDescription += ": " & "存在错误,无法获取信息"
  358. End If
  359. Description = NewDescription
  360. '主 Logo
  361. Logo = If(Entry.Comp Is Nothing, PathImage & "Icons/NoIcon.png", Entry.Comp.GetControlLogo())
  362. '图标右下角的 Logo
  363. If Entry.State = McMod.McModState.Fine Then
  364. If ImgState IsNot Nothing Then
  365. Children.Remove(ImgState)
  366. ImgState = Nothing
  367. End If
  368. Else
  369. If ImgState Is Nothing Then
  370. ImgState = New Image With {
  371. .Width = 20, .Height = 20, .Margin = New Thickness(0, 0, -5, -3), .IsHitTestVisible = False,
  372. .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Bottom
  373. }
  374. RenderOptions.SetBitmapScalingMode(ImgState, BitmapScalingMode.HighQuality)
  375. SetColumn(ImgState, 1) : SetRow(ImgState, 1) : SetRowSpan(ImgState, 2)
  376. Children.Add(ImgState)
  377. '<Image x:Name="ImgState" RenderOptions.BitmapScalingMode="HighQuality" Width="16" Height="16" Margin="0,0,-3,-1"
  378. ' Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" IsHitTestVisible="False"
  379. ' HorizontalAlignment="Right" VerticalAlignment="Bottom"
  380. ' Source="/Images/Icons/Unavaliable.png" />
  381. End If
  382. ImgState.Source = New MyBitmap(PathImage & $"Icons/{Entry.State}.png")
  383. End If
  384. '标签
  385. If Entry.Comp IsNot Nothing Then Tags = Entry.Comp.Tags
  386. End Sub)
  387. End Sub
  388. Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp, Me.Changed
  389. '按钮虚拟化检测
  390. If ButtonHandler IsNot Nothing Then
  391. ButtonHandler(sender, e)
  392. ButtonHandler = Nothing
  393. End If
  394. '触发颜色动画
  395. Dim Time As Integer = If(IsMouseOver, 120, 180)
  396. Dim Ani As New List(Of AniData)
  397. 'ButtonStack
  398. If ButtonStack IsNot Nothing Then
  399. If IsMouseOver Then
  400. Ani.Add(AaOpacity(ButtonStack, 1 - ButtonStack.Opacity, Time * 0.7, Time * 0.3))
  401. Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)),
  402. 5 + Buttons.Count * 25 - ColumnPaddingRight.Width.Value, Time * 0.3, Time * 0.7))
  403. Else
  404. Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.4))
  405. Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)),
  406. 4 - ColumnPaddingRight.Width.Value, Time * 0.4))
  407. End If
  408. End If
  409. 'RectBack
  410. If IsMouseOver OrElse Checked Then
  411. Ani.AddRange({
  412. AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrushBg1"), Time),
  413. AaOpacity(RectBack, 1 - RectBack.Opacity, Time,, New AniEaseOutFluent)
  414. })
  415. If IsMouseDown Then
  416. Ani.Add(AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time * 1.2,, New AniEaseOutFluent))
  417. Else
  418. Ani.Add(AaScaleTransform(RectBack, 1 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time * 1.2,, New AniEaseOutFluent))
  419. End If
  420. Else
  421. Ani.AddRange({
  422. AaOpacity(RectBack, -RectBack.Opacity, Time),
  423. AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent),
  424. AaScaleTransform(RectBack, -0.196, 1,,, True)
  425. })
  426. End If
  427. AniStart(Ani, "LocalModItem Color " & Uuid)
  428. End Sub
  429. End Class