MyLocalModItem.xaml.vb 20 KB

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