using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Media3D; using HandyControl.Data; namespace HandyControl.Controls; public class FlipNumber : Viewport3D { private bool _isLoaded; private TextBlock _page1TextDown; private TextBlock _page2TextUp; private TextBlock _page2TextDown; private TextBlock _page3TextUp; private ContainerUIElement3D _page1; private ContainerUIElement3D _page2; private ContainerUIElement3D _page3; private ContainerUIElement3D _content; private readonly AxisAngleRotation3D _pageRotation3D; private readonly DoubleAnimation _animation; private bool _isAnimating; public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register( nameof(CornerRadius), typeof(CornerRadius), typeof(FlipNumber), new PropertyMetadata(new CornerRadius(4))); public CornerRadius CornerRadius { get => (CornerRadius) GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); } public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register( nameof(Background), typeof(Brush), typeof(FlipNumber), new PropertyMetadata(default(Brush))); public Brush Background { get => (Brush) GetValue(BackgroundProperty); set => SetValue(BackgroundProperty, value); } public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register( nameof(Foreground), typeof(Brush), typeof(FlipNumber), new PropertyMetadata(default(Brush))); public Brush Foreground { get => (Brush) GetValue(ForegroundProperty); set => SetValue(ForegroundProperty, value); } public static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register( nameof(FontSize), typeof(double), typeof(FlipNumber), new PropertyMetadata(70.0)); [TypeConverter(typeof(FontSizeConverter))] public double FontSize { get => (double) GetValue(FontSizeProperty); set => SetValue(FontSizeProperty, value); } public static readonly DependencyProperty NumberProperty = DependencyProperty.Register( nameof(Number), typeof(int), typeof(FlipNumber), new PropertyMetadata(ValueBoxes.Int0Box, OnNumberChanged)); private static void OnNumberChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) => ((FlipNumber) s).OnNumberChanged(); public int Number { get => (int) GetValue(NumberProperty); set => SetValue(NumberProperty, value); } public FlipNumber() { var visual3D = new ModelVisual3D { Content = new DirectionalLight() }; Children.Add(visual3D); _pageRotation3D = new AxisAngleRotation3D { Angle = 0, Axis = new Vector3D(1, 0, 0) }; _animation = new DoubleAnimation(0, 180, new Duration(TimeSpan.FromSeconds(0.8))) { FillBehavior = FillBehavior.Stop }; _animation.Completed += Animation_Completed; Loaded += (s, e) => { if (_isLoaded) return; _isLoaded = true; InitNumber(); var transform3D = new RotateTransform3D(_pageRotation3D); _page2.Transform = transform3D; }; } private void Animation_Completed(object sender, EventArgs e) { _isAnimating = false; UpdateNumber(); } private void InitNumber() { _page1 = new ContainerUIElement3D(); var num1 = Number > 8 ? 0 : Number + 1; var page1NumDown = CreateNumber(num1, false, out _page1TextDown); _page1.Children.Add(page1NumDown); _page2 = new ContainerUIElement3D(); var page2NumUp = CreateNumber(num1, true, out _page2TextUp); var page2NumDown = CreateNumber(Number, false, out _page2TextDown); _page2.Children.Add(page2NumUp); _page2.Children.Add(page2NumDown); _page3 = new ContainerUIElement3D(); var page3NumUp = CreateNumber(Number, true, out _page3TextUp); _page3.Children.Add(page3NumUp); var transform3D = new RotateTransform3D(new AxisAngleRotation3D { Angle = 180, Axis = new Vector3D(1, 0, 0) }); _page3.Transform = transform3D; _content = new ContainerUIElement3D { Children = { _page1, _page2, _page3 } }; Children.Add(_content); } private bool CheckNull() => _page1TextDown != null && _page2TextUp != null && _page2TextDown != null && _page3TextUp != null; private void OnNumberChanged() { if (!CheckNull()) return; InitNewNumber(); if (_isAnimating) { _isAnimating = false; UpdateNumber(); return; } _isAnimating = true; _pageRotation3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, _animation); } private void InitNewNumber() { var num1 = Number.ToString(); _page1TextDown.Text = num1; _page2TextUp.Text = num1; } private void UpdateNumber() { _pageRotation3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, null); _pageRotation3D.Angle = 0; var num = Number.ToString(); _page2TextDown.Text = num; _page3TextUp.Text = num; _isAnimating = false; } private Viewport2DVisual3D CreateNumber(int num, bool isUp, out TextBlock textBlock) { int flag; var rotateTransform = new RotateTransform(); if (isUp) { flag = -1; rotateTransform.Angle = 180; } else { flag = 1; } var halfWidth = ActualWidth / 2; var quarterWidth = ActualWidth / 4; var quarterHeight = ActualHeight / 4; var meMaterial = new DiffuseMaterial(); Viewport2DVisual3D.SetIsVisualHostMaterial(meMaterial, true); textBlock = new TextBlock { RenderTransformOrigin = new Point(0.5, 0.5), Foreground = Foreground, FontSize = FontSize, VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center, TextAlignment = TextAlignment.Center, Text = num.ToString(), RenderTransform = rotateTransform, Margin = new Thickness(0, 0, 0, -quarterHeight), FontFamily = new FontFamily("Consolas") }; var border = new Border { ClipToBounds = true, CornerRadius = new CornerRadius(CornerRadius.TopLeft, CornerRadius.TopRight, 0, 0), Background = Background, Width = halfWidth, Height = quarterHeight, Child = textBlock }; var positions = new Point3DCollection { new(-quarterWidth * flag, quarterHeight, 0), new(-quarterWidth * flag, 0, 0), new(quarterWidth * flag, 0, 0), new(quarterWidth * flag, quarterHeight, 0) }; var triangleIndices = new Int32Collection { 0, 1, 2, 0, 2, 3 }; var textureCoordinates = new PointCollection { new(0, 0), new(0, 1), new(1, 1), new(1, 0) }; var geometry3D = new MeshGeometry3D { Positions = positions, TriangleIndices = triangleIndices, TextureCoordinates = textureCoordinates }; var child = new Viewport2DVisual3D { Geometry = geometry3D, Visual = border, Material = meMaterial }; return child; } }