MyCard.vb 20 KB

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