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("pack://application:,,,/images/Icons/NoIcon.png")
  23. Else
  24. PathLogo.Source = New MyBitmap("pack://application:,,,/images/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("pack://application:,,,/images/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.Disabled
  93. value &= " [禁用]"
  94. Case McMod.McModState.Unavaliable
  95. value &= " [错误]"
  96. End Select
  97. If LabTitle.Text = value Then Exit Property
  98. LabTitle.Text = value
  99. _Title = RawValue
  100. End Set
  101. End Property
  102. '副标题
  103. Public Property SubTitle As String
  104. Get
  105. Return If(LabTitleRaw?.Text, "")
  106. End Get
  107. Set(value As String)
  108. If LabTitleRaw.Text = value Then Exit Property
  109. LabTitleRaw.Text = value
  110. LabTitleRaw.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
  111. End Set
  112. End Property
  113. '描述
  114. Public Property Description As String
  115. Get
  116. Return LabInfo.Text
  117. End Get
  118. Set(value As String)
  119. If LabInfo.Text = value Then Exit Property
  120. LabInfo.Text = value
  121. End Set
  122. End Property
  123. 'Tag
  124. Public WriteOnly Property Tags As List(Of String)
  125. Set(value As List(Of String))
  126. PanTags.Children.Clear()
  127. PanTags.Visibility = If(value.Any(), Visibility.Visible, Visibility.Collapsed)
  128. For Each TagText In value
  129. Dim NewTag = GetObjectFromXML(
  130. "<Border xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
  131. Background=""#11000000"" Padding=""3,1"" CornerRadius=""3"" Margin=""0,0,3,0""
  132. SnapsToDevicePixels=""True"" UseLayoutRounding=""False"">
  133. <TextBlock Text=""" & TagText & """ Foreground=""#868686"" FontSize=""11"" />
  134. </Border>")
  135. PanTags.Children.Add(NewTag)
  136. Next
  137. End Set
  138. End Property
  139. '相关联的 Mod
  140. Public Property Entry As McMod
  141. Get
  142. Return Tag
  143. End Get
  144. Set(value As McMod)
  145. Tag = value
  146. End Set
  147. End Property
  148. '前景色绑定
  149. Public Property Foreground As Brush
  150. Get
  151. Return GetValue(ForegroundProperty)
  152. End Get
  153. Set(value As Brush)
  154. SetValue(ForegroundProperty, value)
  155. End Set
  156. End Property
  157. Public Shared ReadOnly ForegroundProperty As DependencyProperty = DependencyProperty.Register("Foreground", GetType(Brush), GetType(MyLocalModItem), New PropertyMetadata(CType(Color1, SolidColorBrush)))
  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(Me, ForegroundProperty, "ColorBrush2", 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(Me, ForegroundProperty, "ColorBrush1", 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. Foreground = CType(FindResource("ColorBrush2"), SolidColorBrush)
  235. Else
  236. RectCheck.Height = 0
  237. RectCheck.Opacity = 0
  238. Foreground = CType(FindResource("ColorBrush1"), SolidColorBrush)
  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. Private Sub ColorInit(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
  248. SetResourceReference(ForegroundProperty, "ColorBrush1")
  249. End Sub
  250. #End Region
  251. #Region "后加载内容"
  252. '右下角状态指示图标
  253. Private ImgState As Image
  254. '指向背景
  255. Private _RectBack As Border = Nothing
  256. Public ReadOnly Property RectBack As Border
  257. Get
  258. If _RectBack Is Nothing Then
  259. Dim Rect As New Border With {
  260. .Name = "RectBack",
  261. .CornerRadius = New CornerRadius(3),
  262. .RenderTransform = New ScaleTransform(0.8, 0.8),
  263. .RenderTransformOrigin = New Point(0.5, 0.5),
  264. .BorderThickness = New Thickness(GetWPFSize(1)),
  265. .SnapsToDevicePixels = True,
  266. .IsHitTestVisible = False,
  267. .Opacity = 0
  268. }
  269. Rect.SetResourceReference(Border.BackgroundProperty, "ColorBrush7")
  270. Rect.SetResourceReference(Border.BorderBrushProperty, "ColorBrush6")
  271. SetColumnSpan(Rect, 999)
  272. SetRowSpan(Rect, 999)
  273. Children.Insert(0, Rect)
  274. _RectBack = Rect
  275. '<!--<Border x:Name = "RectBack" CornerRadius="3" RenderTransformOrigin="0.5,0.5" SnapsToDevicePixels="True"
  276. 'IsHitTestVisible = "False" Opacity="0" BorderThickness="1"
  277. 'Grid.ColumnSpan = "4" Background="{DynamicResource ColorBrush7}" BorderBrush="{DynamicResource ColorBrush6}"/>-->
  278. End If
  279. Return _RectBack
  280. End Get
  281. End Property
  282. '按钮
  283. Public ButtonHandler As Action(Of MyLocalModItem, EventArgs)
  284. Public ButtonStack As FrameworkElement
  285. Private _Buttons As IEnumerable(Of MyIconButton)
  286. Public Property Buttons As IEnumerable(Of MyIconButton)
  287. Get
  288. Return _Buttons
  289. End Get
  290. Set(value As IEnumerable(Of MyIconButton))
  291. _Buttons = value
  292. '移除原 Stack
  293. If ButtonStack IsNot Nothing Then
  294. Children.Remove(ButtonStack)
  295. ButtonStack = Nothing
  296. End If
  297. If value.Count = 0 Then Exit Property
  298. '添加新 Stack
  299. ButtonStack = New StackPanel With {.Opacity = 0, .Margin = New Thickness(0, 0, 5, 0), .SnapsToDevicePixels = False, .Orientation = Orientation.Horizontal,
  300. .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Center, .UseLayoutRounding = False}
  301. SetColumnSpan(ButtonStack, 10) : SetRowSpan(ButtonStack, 10)
  302. '构造按钮
  303. For Each Btn As MyIconButton In value
  304. If Btn.Height.Equals(Double.NaN) Then Btn.Height = 25
  305. If Btn.Width.Equals(Double.NaN) Then Btn.Width = 25
  306. CType(ButtonStack, StackPanel).Children.Add(Btn)
  307. Next
  308. Children.Add(ButtonStack)
  309. End Set
  310. End Property
  311. '勾选条
  312. Private _RectCheck As Border
  313. Public ReadOnly Property RectCheck As Border
  314. Get
  315. If _RectCheck Is Nothing Then
  316. _RectCheck = New Border With {.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2),
  317. .VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center),
  318. .HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False,
  319. .Margin = If(Checked, New Thickness(-3, 6, 0, 6), New Thickness(-3, 0, 0, 0))}
  320. _RectCheck.SetResourceReference(Border.BackgroundProperty, "ColorBrush3")
  321. SetRowSpan(_RectCheck, 10)
  322. Children.Add(_RectCheck)
  323. End If
  324. Return _RectCheck
  325. End Get
  326. End Property
  327. #End Region
  328. Public Sub Refresh()
  329. RunInUi(
  330. Sub()
  331. '标题
  332. If Entry.Comp Is Nothing Then
  333. Title = Entry.Name
  334. SubTitle = If(Entry.Version Is Nothing, "", " | " & Entry.Version)
  335. Else
  336. Dim Titles = Entry.Comp.GetControlTitle(False)
  337. Title = Titles.Key
  338. SubTitle = Titles.Value & If(Entry.Version Is Nothing, "", " | " & Entry.Version)
  339. End If
  340. '描述
  341. Dim NewDescription As String
  342. Select Case Entry.State
  343. Case McMod.McModState.Fine
  344. NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path)
  345. Case McMod.McModState.Disabled
  346. NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path.Substring(0, Entry.Path.Count - ".disabled".Count))
  347. Case Else 'McMod.McModState.Unavaliable
  348. NewDescription = GetFileNameFromPath(Entry.Path)
  349. End Select
  350. If Entry.Comp IsNot Nothing Then
  351. NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "")
  352. ElseIf Entry.Description IsNot Nothing Then
  353. NewDescription += ": " & Entry.Description.Replace(vbCr, "").Replace(vbLf, "")
  354. ElseIf Not Entry.IsFileAvailable Then
  355. NewDescription += ": " & "存在错误,无法获取信息"
  356. End If
  357. Description = NewDescription
  358. '主 Logo
  359. Logo = If(Entry.Comp Is Nothing, "pack://application:,,,/images/Icons/NoIcon.png", Entry.Comp.GetControlLogo())
  360. '图标右下角的 Logo
  361. If Entry.State = McMod.McModState.Fine Then
  362. If ImgState IsNot Nothing Then
  363. Children.Remove(ImgState)
  364. ImgState = Nothing
  365. End If
  366. Else
  367. If ImgState Is Nothing Then
  368. ImgState = New Image With {
  369. .Width = 17, .Height = 17, .Margin = New Thickness(0, 0, -4, -3), .IsHitTestVisible = False,
  370. .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Bottom
  371. }
  372. RenderOptions.SetBitmapScalingMode(ImgState, BitmapScalingMode.HighQuality)
  373. SetColumn(ImgState, 1) : SetRow(ImgState, 1) : SetRowSpan(ImgState, 2)
  374. Children.Add(ImgState)
  375. '<Image x:Name="ImgState" RenderOptions.BitmapScalingMode="HighQuality" Width="16" Height="16" Margin="0,0,-3,-1"
  376. ' Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" IsHitTestVisible="False"
  377. ' HorizontalAlignment="Right" VerticalAlignment="Bottom"
  378. ' Source="/Images/Icons/Unavaliable.png" />
  379. End If
  380. ImgState.Source = New MyBitmap($"pack://application:,,,/images/Icons/{Entry.State}.png")
  381. End If
  382. '标签
  383. If Entry.Comp IsNot Nothing Then Tags = Entry.Comp.Tags
  384. End Sub)
  385. End Sub
  386. Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp, Me.Changed
  387. '按钮虚拟化检测
  388. If ButtonHandler IsNot Nothing Then
  389. ButtonHandler(sender, e)
  390. ButtonHandler = Nothing
  391. End If
  392. '触发颜色动画
  393. Dim Time As Integer = If(IsMouseOver, 120, 180)
  394. Dim Ani As New List(Of AniData)
  395. 'ButtonStack
  396. If ButtonStack IsNot Nothing Then
  397. If IsMouseOver Then
  398. Ani.Add(AaOpacity(ButtonStack, 1 - ButtonStack.Opacity, Time * 0.7, Time * 0.3))
  399. Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)),
  400. 5 + Buttons.Count * 25 - ColumnPaddingRight.Width.Value, Time * 0.3, Time * 0.7))
  401. Else
  402. Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.4))
  403. Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)),
  404. 4 - ColumnPaddingRight.Width.Value, Time * 0.4))
  405. End If
  406. End If
  407. 'RectBack
  408. If IsMouseOver OrElse Checked Then
  409. Ani.AddRange({
  410. AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrushBg1"), Time),
  411. AaOpacity(RectBack, 1 - RectBack.Opacity, Time,, New AniEaseOutFluent)
  412. })
  413. If IsMouseDown Then
  414. Ani.Add(AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time * 1.2,, New AniEaseOutFluent))
  415. Else
  416. Ani.Add(AaScaleTransform(RectBack, 1 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time * 1.2,, New AniEaseOutFluent))
  417. End If
  418. Else
  419. Ani.AddRange({
  420. AaOpacity(RectBack, -RectBack.Opacity, Time),
  421. AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent),
  422. AaScaleTransform(RectBack, -0.196, 1,,, True)
  423. })
  424. End If
  425. AniStart(Ani, "LocalModItem Color " & Uuid)
  426. End Sub
  427. End Class