MyCard.vb 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. Public Class MyCard
  2. '控件
  3. Inherits Grid
  4. Private ReadOnly MainGrid As Grid
  5. Private ReadOnly MainChrome As SystemDropShadowChrome
  6. Private ReadOnly MainBorder As Border
  7. Public Property BorderChild As UIElement
  8. Get
  9. Return MainBorder.Child
  10. End Get
  11. Set(value As UIElement)
  12. MainBorder.Child = value
  13. End Set
  14. End Property
  15. Private _MainTextBlock As TextBlock
  16. Public Property MainTextBlock As TextBlock
  17. Get
  18. Init() '当父级触发 Loaded 时,本卡片可能尚未触发 Loaded(该事件从父级向子级调用),因此这会是 null。手动触发以确保控件已加载。
  19. Return _MainTextBlock
  20. End Get
  21. Set(value As TextBlock)
  22. _MainTextBlock = value
  23. End Set
  24. End Property
  25. Private _MainSwap As Shapes.Path
  26. Public Property MainSwap As Shapes.Path
  27. Get
  28. Init()
  29. Return _MainSwap
  30. End Get
  31. Set(value As Shapes.Path)
  32. _MainSwap = value
  33. End Set
  34. End Property
  35. '属性
  36. Public Uuid As Integer = GetUuid()
  37. Public Property Title As String
  38. Get
  39. Return GetValue(TitleProperty)
  40. End Get
  41. Set(value As String)
  42. SetValue(TitleProperty, value)
  43. If _MainTextBlock IsNot Nothing Then MainTextBlock.Text = value
  44. End Set
  45. End Property
  46. Public Shared ReadOnly TitleProperty As DependencyProperty = DependencyProperty.Register("Title", GetType(String), GetType(MyCard), New PropertyMetadata(""))
  47. 'UI 建立
  48. Public Sub New()
  49. MainChrome = New SystemDropShadowChrome With {.Margin = New Thickness(-9.5, -9, 0.5, -0.5), .Opacity = 0.1, .CornerRadius = New CornerRadius(6)}
  50. MainChrome.SetResourceReference(SystemDropShadowChrome.ColorProperty, "ColorObject1")
  51. Children.Insert(0, MainChrome)
  52. MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(205, 255, 255, 255)), .CornerRadius = New CornerRadius(6), .IsHitTestVisible = False}
  53. Children.Insert(1, MainBorder)
  54. MainGrid = New Grid
  55. Children.Add(MainGrid)
  56. End Sub
  57. Private IsLoad As Boolean = False
  58. Private Sub Init() Handles Me.Loaded
  59. If IsLoad Then Exit Sub
  60. IsLoad = True
  61. '初次加载限定
  62. If Title <> "" AndAlso MainTextBlock Is Nothing Then
  63. MainTextBlock = New TextBlock With {.HorizontalAlignment = HorizontalAlignment.Left, .VerticalAlignment = VerticalAlignment.Top, .Margin = New Thickness(15, 12, 0, 0), .FontWeight = FontWeights.Bold, .FontSize = 13, .IsHitTestVisible = False}
  64. MainTextBlock.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrush1")
  65. MainTextBlock.SetBinding(TextBlock.TextProperty, New Binding("Title") With {.Source = Me, .Mode = BindingMode.OneWay})
  66. MainGrid.Children.Add(MainTextBlock)
  67. End If
  68. If CanSwap OrElse SwapControl IsNot Nothing Then
  69. If SwapControl Is Nothing AndAlso Children.Count > 3 Then SwapControl = Children(3)
  70. MainSwap = New Shapes.Path With {.HorizontalAlignment = HorizontalAlignment.Right, .Stretch = Stretch.Uniform, .Height = 6, .Width = 10, .VerticalAlignment = VerticalAlignment.Top, .Margin = New Thickness(0, 17, 16, 0), .Data = New GeometryConverter().ConvertFromString("M2,4 l-2,2 10,10 10,-10 -2,-2 -8,8 -8,-8 z"), .RenderTransform = New RotateTransform(180), .RenderTransformOrigin = New Point(0.5, 0.5)}
  71. MainSwap.SetResourceReference(Shapes.Path.FillProperty, "ColorBrush1")
  72. MainGrid.Children.Add(MainSwap)
  73. End If
  74. '改变默认的折叠
  75. If IsSwaped AndAlso SwapControl IsNot Nothing Then
  76. MainSwap.RenderTransform = New RotateTransform(If(SwapLogoRight, 270, 0))
  77. '取消由于高度变化被迫触发的高度动画
  78. Dim RawUseAnimation As Boolean = UseAnimation
  79. UseAnimation = False
  80. Height = SwapedHeight
  81. AniStop("MyCard Height " & Uuid)
  82. IsHeightAnimating = False
  83. RunInUi(Sub() UseAnimation = RawUseAnimation, True)
  84. End If
  85. End Sub
  86. Public Sub StackInstall()
  87. StackInstall(SwapControl, SwapType, Title)
  88. TriggerForceResize()
  89. End Sub
  90. Public Shared Sub StackInstall(ByRef Stack As StackPanel, Type As Integer, Optional CardTitle As String = "")
  91. '这一部分的代码是好几年前留下的究极屎坑,当时还不知道该咋正确调用这种方法,就写了这么一坨屎
  92. '但是现在……反正勉强能用……懒得改了就这样吧.jpg
  93. '别骂了别骂了.jpg
  94. If IsNothing(Stack.Tag) Then Exit Sub
  95. '排序
  96. Select Case Type
  97. Case 3
  98. Stack.Tag = Sort(CType(Stack.Tag, List(Of DlOptiFineListEntry)), Function(Left As DlOptiFineListEntry, Right As DlOptiFineListEntry) As Boolean
  99. Return VersionSortBoolean(Left.NameDisplay, Right.NameDisplay)
  100. End Function)
  101. Case 4, 10
  102. Stack.Tag = Sort(CType(Stack.Tag, List(Of DlLiteLoaderListEntry)), Function(Left As DlLiteLoaderListEntry, Right As DlLiteLoaderListEntry) As Boolean
  103. Return VersionSortBoolean(Left.Inherit, Right.Inherit)
  104. End Function)
  105. Case 6
  106. Stack.Tag = Sort(CType(Stack.Tag, List(Of DlForgeVersionEntry)), Function(Left As DlForgeVersionEntry, Right As DlForgeVersionEntry) As Boolean
  107. Return VersionSortBoolean(Left.Version, Right.Version)
  108. End Function)
  109. Case 8, 9
  110. Stack.Tag = Sort(CType(Stack.Tag, List(Of CompFile)), Function(Left As CompFile, Right As CompFile) As Boolean
  111. Return Left.ReleaseDate > Right.ReleaseDate
  112. End Function)
  113. End Select
  114. '控件转换
  115. Select Case Type
  116. Case 5
  117. Dim LoadingPickaxe As New MyLoading With {.Text = "正在获取版本列表", .Margin = New Thickness(5)}
  118. Dim Loader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Main", AddressOf DlForgeVersionMain)
  119. LoadingPickaxe.State = Loader
  120. Loader.Start(Stack.Tag)
  121. AddHandler LoadingPickaxe.StateChanged, AddressOf FrmDownloadForge.Forge_StateChanged
  122. AddHandler LoadingPickaxe.Click, AddressOf FrmDownloadForge.Forge_Click
  123. Stack.Children.Add(LoadingPickaxe)
  124. Case 6
  125. ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True)
  126. Case 8
  127. CompFilesCardPreload(Stack, Stack.Tag)
  128. Case 13
  129. Dim LoadingPickaxe As New MyLoading With {.Text = "正在获取版本列表", .Margin = New Thickness(5)}
  130. Dim Loader = New LoaderTask(Of String, List(Of DlNeoForgeVersionEntry))("DlNeoForgeVersion Main", AddressOf DlNeoForgeVersionMain)
  131. LoadingPickaxe.State = Loader
  132. Loader.Start(Stack.Tag)
  133. AddHandler LoadingPickaxe.StateChanged, AddressOf FrmDownloadNeoForge.NeoForge_StateChanged
  134. AddHandler LoadingPickaxe.Click, AddressOf FrmDownloadNeoForge.NeoForge_Click
  135. Stack.Children.Add(LoadingPickaxe)
  136. Case 14
  137. NeoForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf NeoForgeSave_Click, True)
  138. End Select
  139. '实现控件虚拟化
  140. For Each Data As Object In Stack.Tag
  141. Select Case Type
  142. Case 0
  143. Stack.Children.Add(PageSelectRight.McVersionListItem(Data))
  144. Case 2
  145. Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True))
  146. Case 3
  147. Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True))
  148. Case 4
  149. Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf FrmDownloadLiteLoader.DownloadStart, False))
  150. Case 5
  151. Case 6
  152. Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True))
  153. Case 7
  154. Stack.Children.Add(McDownloadListItem(Data, Sub(sender, e) '不能使用 AddressOf,这导致了 #535,原因完全不明,疑似是编译器 Bug
  155. FrmDownloadInstall.MinecraftSelected(sender, e)
  156. End Sub, False))
  157. Case 8
  158. If CType(Stack.Tag, List(Of CompFile)).Distinct(Function(a, b) a.DisplayName = b.DisplayName).Count <>
  159. CType(Stack.Tag, List(Of CompFile)).Count Then
  160. '存在重复的名称(#1344)
  161. Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=True))
  162. Else
  163. '不存在重复的名称,正常加载
  164. Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Save_Click))
  165. End If
  166. Case 9
  167. If CType(Stack.Tag, List(Of CompFile)).Distinct(Function(a, b) a.DisplayName = b.DisplayName).Count <>
  168. CType(Stack.Tag, List(Of CompFile)).Count Then
  169. '存在重复的名称(#1344)
  170. Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=True))
  171. Else
  172. '不存在重复的名称,正常加载
  173. Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click))
  174. End If
  175. Case 10
  176. Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf LiteLoaderSave_Click, True))
  177. Case 11
  178. Stack.Children.Add(CType(Data, HelpEntry).ToListItem)
  179. Case 12
  180. Stack.Children.Add(FabricDownloadListItem(CType(Data, JObject), AddressOf FrmDownloadInstall.Fabric_Selected))
  181. Case 13
  182. Case 14
  183. Stack.Children.Add(NeoForgeDownloadListItem(Data, AddressOf NeoForgeSave_Click, True))
  184. Case Else
  185. Log("未知的虚拟化种类:" & Type, LogLevel.Feedback)
  186. End Select
  187. Next
  188. Stack.Children.Add(New FrameworkElement With {.Height = 18}) '下边距,同时适应折叠
  189. Stack.Tag = Nothing
  190. End Sub
  191. '事件
  192. Public Property HasMouseAnimation As Boolean = True
  193. Private Sub MyCard_MouseEnter(sender As Object, e As MouseEventArgs) Handles Me.MouseEnter
  194. If Not HasMouseAnimation Then Exit Sub
  195. Dim AniList As New List(Of AniData)
  196. If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 150))
  197. If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 150))
  198. AniList.AddRange({
  199. AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject2", 180),
  200. AaColor(MainBorder, Border.BackgroundProperty, New MyColor(230, 255, 255, 255) - MainBorder.Background, 180),
  201. AaOpacity(MainChrome, 0.3 - MainChrome.Opacity, 180)
  202. })
  203. AniStart(AniList, "MyCard Mouse " & Uuid)
  204. End Sub
  205. Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
  206. If Not HasMouseAnimation Then Exit Sub
  207. Dim AniList As New List(Of AniData)
  208. If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 250))
  209. If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 250))
  210. AniList.AddRange({
  211. AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject1", 300),
  212. AaColor(MainBorder, Border.BackgroundProperty, New MyColor(205, 255, 255, 255) - MainBorder.Background, 300),
  213. AaOpacity(MainChrome, 0.1 - MainChrome.Opacity, 300)
  214. })
  215. AniStart(AniList, "MyCard Mouse " & Uuid)
  216. End Sub
  217. #Region "高度改变动画"
  218. ''' <summary>
  219. ''' 是否启用高度改变动画。
  220. ''' </summary>
  221. Public Property UseAnimation As Boolean = True
  222. Private IsHeightAnimating As Boolean = False
  223. Private ActualUsedHeight As Double '回滚实际高度(例如 NaN)
  224. Private Sub MySizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged
  225. If Not UseAnimation Then Exit Sub
  226. Dim DeltaHeight As Double = If(IsSwaped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height
  227. '卡片的进入时动画已被页面通用切换动画替代
  228. If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Exit Sub
  229. StartHeightAnimation(DeltaHeight, e.PreviousSize.Height, False)
  230. End Sub
  231. Private Sub StartHeightAnimation(DeltaHeight As Double, PreviousHeight As Double, IsLoadAnimation As Boolean)
  232. If IsHeightAnimating OrElse FrmMain Is Nothing Then Exit Sub '避免 XAML 设计器出错
  233. Dim AnimList As New List(Of AniData)
  234. If DeltaHeight > 10 OrElse (DeltaHeight < -10 AndAlso Not IsNothing(SwapControl)) Then '如果不是需要折叠的卡片,高度减小时的弹跳会吞掉按钮下边框
  235. '高度增加较大,使用弹起动画
  236. Dim Delta As Double = MathClamp(Math.Abs(DeltaHeight) * 0.05, 3, 10) * Math.Sign(DeltaHeight)
  237. AnimList.AddRange({
  238. AaHeight(Me, DeltaHeight + Delta, 300, If(IsLoadAnimation, 30, 0), If(DeltaHeight > FrmMain.Height, New AniEaseInFluent(AniEasePower.ExtraStrong), New AniEaseOutFluent(AniEasePower.ExtraStrong))),
  239. AaHeight(Me, -Delta, 150, 260, Ease:=New AniEaseOutFluent(AniEasePower.Strong))
  240. })
  241. Else
  242. '普通的改变就行啦
  243. AnimList.AddRange({
  244. AaHeight(Me, DeltaHeight, MathClamp(Math.Abs(DeltaHeight) * 4, 150, 250),, New AniEaseOutFluent)
  245. })
  246. End If
  247. AnimList.Add(AaCode(Sub()
  248. IsHeightAnimating = False
  249. Height = ActualUsedHeight
  250. If IsSwaped Then SwapControl.Visibility = Visibility.Collapsed
  251. End Sub,, True))
  252. AniStart(AnimList, "MyCard Height " & Uuid)
  253. IsHeightAnimating = True
  254. ActualUsedHeight = If(IsSwaped, SwapedHeight, Height)
  255. Height = PreviousHeight
  256. End Sub
  257. ''' <summary>
  258. ''' 通知 MyCard,控件内容已改变,需要中断动画并更新高度。
  259. ''' </summary>
  260. Public Sub TriggerForceResize()
  261. Height = If(IsSwaped, SwapedHeight, Double.NaN)
  262. AniStop("MyCard Height " & Uuid)
  263. IsHeightAnimating = False
  264. End Sub
  265. #End Region
  266. '折叠
  267. '若设置了 CanSwap,或 SwapControl 不为空,则判定为会进行折叠
  268. '这是因为不能直接在 XAML 中设置 SwapControl
  269. Public SwapControl As Object
  270. Public Property CanSwap As Boolean = False
  271. ''' <summary>
  272. ''' 被折叠的种类,用于控件虚拟化。
  273. ''' </summary>
  274. Public Property SwapType As Integer
  275. ''' <summary>
  276. ''' 是否已被折叠。
  277. ''' </summary>
  278. Public Property IsSwaped As Boolean
  279. Get
  280. Return _IsSwaped
  281. End Get
  282. Set(value As Boolean)
  283. If _IsSwaped = value Then Exit Property
  284. _IsSwaped = value
  285. If SwapControl Is Nothing Then Exit Property
  286. '展开
  287. If Not IsSwaped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title)
  288. '若尚未加载,会在 Loaded 事件中触发无动画的折叠,不需要在这里进行
  289. If Not IsLoaded Then Exit Property
  290. '更新高度
  291. SwapControl.Visibility = Visibility.Visible
  292. TriggerForceResize()
  293. '改变箭头
  294. AniStart(AaRotateTransform(MainSwap, If(_IsSwaped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 400,, New AniEaseOutBack(AniEasePower.Weak)), "MyCard Swap " & Uuid, True)
  295. End Set
  296. End Property
  297. Private _IsSwaped As Boolean = False
  298. Public Property SwapLogoRight As Boolean = False
  299. Private IsMouseDown As Boolean = False
  300. Public Event PreviewSwap(sender As Object, e As RouteEventArgs)
  301. Public Event Swap(sender As Object, e As RouteEventArgs)
  302. Public Const SwapedHeight As Integer = 40
  303. Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
  304. Dim Pos As Double = Mouse.GetPosition(Me).Y
  305. If Not IsSwaped AndAlso (IsNothing(SwapControl) OrElse Pos > SwapedHeight OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判
  306. IsMouseDown = True
  307. End Sub
  308. Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
  309. If IsMouseDown Then
  310. IsMouseDown = False
  311. Dim Pos As Double = Mouse.GetPosition(Me).Y
  312. If Not IsSwaped AndAlso (IsNothing(SwapControl) OrElse Pos > SwapedHeight OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判
  313. Dim ee = New RouteEventArgs(True)
  314. RaiseEvent PreviewSwap(Me, ee)
  315. If ee.Handled Then
  316. IsMouseDown = False
  317. Exit Sub
  318. End If
  319. IsSwaped = Not IsSwaped
  320. Log("[Control] " & If(IsSwaped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title))
  321. RaiseEvent Swap(Me, ee)
  322. End If
  323. End Sub
  324. Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
  325. IsMouseDown = False
  326. End Sub
  327. End Class
  328. Partial Public Module ModAnimation
  329. Public Sub AniDispose(Control As MyCard, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
  330. If Control.IsHitTestVisible Then
  331. Control.IsHitTestVisible = False
  332. AniStart({
  333. AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
  334. AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
  335. AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
  336. AaCode(Sub()
  337. If RemoveFromChildren Then
  338. If Control.Parent Is Nothing Then Exit Sub
  339. CType(Control.Parent, Object).Children.Remove(Control)
  340. Else
  341. Control.Visibility = Visibility.Collapsed
  342. End If
  343. If CallBack IsNot Nothing Then CallBack(Control)
  344. End Sub,, True)
  345. }, "MyCard Dispose " & Control.Uuid)
  346. Else
  347. If RemoveFromChildren Then
  348. If Control.Parent Is Nothing Then Exit Sub
  349. CType(Control.Parent, Object).Children.Remove(Control)
  350. Else
  351. Control.Visibility = Visibility.Collapsed
  352. End If
  353. If CallBack IsNot Nothing Then CallBack(Control)
  354. End If
  355. End Sub
  356. End Module