mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-03-02 15:50:51 +08:00
页面视图新增缩略图模式
This commit is contained in:
100
AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Carousel.xaml
Normal file
100
AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Carousel.xaml
Normal file
@@ -0,0 +1,100 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:AIStudio.Wpf.DiagramDesigner.Additionals.Controls"
|
||||
xmlns:o="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options">
|
||||
|
||||
<Geometry o:Freeze="True" x:Key="LeftGeometry">M394.24 512L683.52 248.32c10.24-10.24 10.24-25.6 0-35.84-10.24-10.24-25.6-10.24-35.84 0l-307.2 279.04c-5.12 5.12-7.68 12.8-7.68 20.48 0 7.68 2.56 15.36 7.68 20.48l307.2 279.04c10.24 10.24 25.6 10.24 35.84 0 10.24-10.24 10.24-25.6 0-35.84L394.24 512z</Geometry>
|
||||
<Geometry o:Freeze="True" x:Key="RightGeometry">M4.1666641,0 C5.2083321,0 6.25,0.41666794 7.0833321,1.25 L57.083331,46.666664 C57.916664,47.499998 58.33333,48.749998 58.333329,49.999998 58.33333,51.249997 57.916664,52.499997 57.083331,53.333331 L7.0833321,98.749996 C5.4166641,100.41666 2.9166641,100.41666 1.2499962,98.749996 -0.41666794,97.083328 -0.41666794,94.583328 1.2499962,92.916664 L48.333331,49.999998 1.2499962,7.0833321 C-0.41666794,5.4166641 -0.41666794,2.9166641 1.2499962,1.25 2.0833282,0.41666794 3.1249962,0 4.1666641,0 z</Geometry>
|
||||
|
||||
<Style x:Key="CarouselListBoxItemStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Height" Value="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
|
||||
</Style>
|
||||
|
||||
<ItemsPanelTemplate x:Key="CarouselListBoxItemPanelTemplate">
|
||||
<StackPanel FocusVisualStyle="{x:Null}" Orientation="Horizontal" HorizontalAlignment="Left"/>
|
||||
</ItemsPanelTemplate>
|
||||
|
||||
<Style x:Key="CarouselListBox" TargetType="ListBox">
|
||||
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="False"/>
|
||||
<Setter Property="ItemsPanel" Value="{StaticResource CarouselListBoxItemPanelTemplate}"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource CarouselListBoxItemStyle}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CarouselPateButton" TargetType="RadioButton">
|
||||
<Setter Property="Margin" Value="5,0"/>
|
||||
<Setter Property="Width" Value="10"/>
|
||||
<Setter Property="Height" Value="10"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="{Binding Path=BorderBrush, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=controls:Carousel}}"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border CornerRadius="5" SnapsToDevicePixels="true" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}"/>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Opacity" Value=".9"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Opacity" Value=".6"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Background" Value="{Binding Path=BorderBrush, RelativeSource={RelativeSource Mode=Self}}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CarouselStyle" TargetType="controls:Carousel" BasedOn="{StaticResource CarouselListBox}">
|
||||
<Setter Property="PageButtonStyle" Value="{StaticResource CarouselPateButton}"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="controls:Carousel">
|
||||
<ControlTemplate.Triggers>
|
||||
<EventTrigger RoutedEvent="FrameworkElement.MouseEnter">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="GridTop" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:.1"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="FrameworkElement.MouseLeave">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="GridTop" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:.1"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
<Grid ClipToBounds="True">
|
||||
<ItemsPresenter x:Name="PART_ItemsControl" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" HorizontalAlignment="Left"/>
|
||||
<Grid x:Name="GridTop" Opacity="0">
|
||||
<Button x:Name="Part_PreButton" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="30,0,0,0" Style="{StaticResource AIStudio.Styles.Button.Plain}">
|
||||
<Border Width="50" Height="100" Background="{TemplateBinding Background}" CornerRadius="4">
|
||||
<Path Margin="16" Fill="White" Data="{StaticResource LeftGeometry}" Stretch="Uniform"/>
|
||||
</Border>
|
||||
</Button>
|
||||
<Button x:Name="Part_NextButton" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,30,0" Style="{StaticResource AIStudio.Styles.Button.Plain}">
|
||||
<Border Width="50" Height="100" Background="{TemplateBinding Background}" CornerRadius="4">
|
||||
<Path Margin="16" Fill="White" Data="{StaticResource RightGeometry}" Stretch="Uniform"/>
|
||||
</Border>
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel Name="PART_PanelPage" Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,20"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource CarouselStyle}" TargetType="{x:Type controls:Carousel}"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,336 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Threading;
|
||||
|
||||
|
||||
namespace AIStudio.Wpf.DiagramDesigner.Additionals.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 轮播控件
|
||||
/// </summary>
|
||||
[DefaultProperty("Items")]
|
||||
[ContentProperty("Items")]
|
||||
[TemplatePart(Name = ElementPanelPage, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementItemsControl, Type = typeof(ItemsPresenter))]
|
||||
[TemplatePart(Name = Part_PreButton, Type = typeof(Button))]
|
||||
[TemplatePart(Name = Part_NextButton, Type = typeof(Button))]
|
||||
public class Carousel : ListBox, IDisposable
|
||||
{
|
||||
#region Constants
|
||||
private const string ElementPanelPage = "PART_PanelPage";
|
||||
private const string ElementItemsControl = "PART_ItemsControl";
|
||||
private const string Part_PreButton = "Part_PreButton";
|
||||
private const string Part_NextButton = "Part_NextButton";
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
private Panel _panelPage;
|
||||
|
||||
private bool _appliedTemplate;
|
||||
|
||||
private ItemsPresenter _itemsControl;
|
||||
|
||||
private Button _btnPre;
|
||||
private Button _btnNext;
|
||||
|
||||
private int _pageIndex = -1;
|
||||
|
||||
private RadioButton _selectedButton;
|
||||
|
||||
private DispatcherTimer _updateTimer;
|
||||
|
||||
private readonly List<double> _widthList = new List<double>();
|
||||
|
||||
#endregion Data
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_appliedTemplate = false;
|
||||
|
||||
_panelPage?.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ButtonPages_OnClick));
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_itemsControl = GetTemplateChild(ElementItemsControl) as ItemsPresenter;
|
||||
_panelPage = GetTemplateChild(ElementPanelPage) as Panel;
|
||||
|
||||
if (_btnPre != null)
|
||||
{
|
||||
_btnPre.Click -= ButtonPrev_OnClick;
|
||||
}
|
||||
if (_btnNext != null)
|
||||
{
|
||||
_btnNext.Click -= ButtonNext_OnClick;
|
||||
}
|
||||
|
||||
_btnPre = GetTemplateChild(Part_PreButton) as Button;
|
||||
_btnNext = GetTemplateChild(Part_NextButton) as Button;
|
||||
|
||||
if (_btnPre != null)
|
||||
{
|
||||
_btnPre.Click += ButtonPrev_OnClick;
|
||||
}
|
||||
if (_btnNext != null)
|
||||
{
|
||||
_btnNext.Click += ButtonNext_OnClick;
|
||||
}
|
||||
|
||||
if (!CheckNull()) return;
|
||||
|
||||
_panelPage.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ButtonPages_OnClick));
|
||||
_appliedTemplate = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
TimerSwitch(AutoRun);
|
||||
UpdatePageButtons(_pageIndex);
|
||||
}
|
||||
|
||||
private bool CheckNull() => !(_itemsControl == null || _panelPage == null);
|
||||
|
||||
public static readonly DependencyProperty AutoRunProperty = DependencyProperty.Register(
|
||||
"AutoRun", typeof(bool), typeof(Carousel), new PropertyMetadata(false, (o, args) => {
|
||||
var ctl = (Carousel)o;
|
||||
ctl.TimerSwitch((bool)args.NewValue);
|
||||
}));
|
||||
|
||||
public static readonly DependencyProperty IntervalProperty = DependencyProperty.Register(
|
||||
"Interval", typeof(TimeSpan), typeof(Carousel), new PropertyMetadata(TimeSpan.FromSeconds(2)));
|
||||
|
||||
public static readonly DependencyProperty ExtendWidthProperty = DependencyProperty.Register(
|
||||
"ExtendWidth", typeof(double), typeof(Carousel), new PropertyMetadata(0d));
|
||||
|
||||
public double ExtendWidth
|
||||
{
|
||||
get => (double)GetValue(ExtendWidthProperty);
|
||||
set => SetValue(ExtendWidthProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsCenterProperty = DependencyProperty.Register(
|
||||
"IsCenter", typeof(bool), typeof(Carousel), new PropertyMetadata(false));
|
||||
|
||||
public bool IsCenter
|
||||
{
|
||||
get => (bool)GetValue(IsCenterProperty);
|
||||
set => SetValue(IsCenterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PageButtonStyleProperty = DependencyProperty.Register(
|
||||
"PageButtonStyle", typeof(Style), typeof(Carousel), new PropertyMetadata(default(Style)));
|
||||
|
||||
public Style PageButtonStyle
|
||||
{
|
||||
get => (Style)GetValue(PageButtonStyleProperty);
|
||||
set => SetValue(PageButtonStyleProperty, value);
|
||||
}
|
||||
|
||||
public Carousel()
|
||||
{
|
||||
Loaded += (s, e) => UpdatePageButtons();
|
||||
IsVisibleChanged += Carousel_IsVisibleChanged;
|
||||
}
|
||||
|
||||
~Carousel() => Dispose();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
IsVisibleChanged -= Carousel_IsVisibleChanged;
|
||||
_updateTimer.Stop();
|
||||
_isDisposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Carousel_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (_updateTimer == null) return;
|
||||
|
||||
if (IsVisible)
|
||||
{
|
||||
_updateTimer.Tick += UpdateTimer_Tick;
|
||||
_updateTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateTimer.Stop();
|
||||
_updateTimer.Tick -= UpdateTimer_Tick;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
base.OnItemsChanged(e);
|
||||
|
||||
UpdatePageButtons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动跳转
|
||||
/// </summary>
|
||||
public bool AutoRun
|
||||
{
|
||||
get => (bool)GetValue(AutoRunProperty);
|
||||
set => SetValue(AutoRunProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 跳转时间间隔
|
||||
/// </summary>
|
||||
public TimeSpan Interval
|
||||
{
|
||||
get => (TimeSpan)GetValue(IntervalProperty);
|
||||
set => SetValue(IntervalProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 页码
|
||||
/// </summary>
|
||||
public int PageIndex
|
||||
{
|
||||
get => _pageIndex;
|
||||
set
|
||||
{
|
||||
if (Items.Count == 0) return;
|
||||
if (_pageIndex == value) return;
|
||||
if (value < 0)
|
||||
_pageIndex = Items.Count - 1;
|
||||
else if (value >= Items.Count)
|
||||
_pageIndex = 0;
|
||||
else
|
||||
_pageIndex = value;
|
||||
UpdatePageButtons(_pageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计时器开关
|
||||
/// </summary>
|
||||
private void TimerSwitch(bool run)
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
_updateTimer.Tick -= UpdateTimer_Tick;
|
||||
_updateTimer.Stop();
|
||||
_updateTimer = null;
|
||||
}
|
||||
|
||||
if (!run) return;
|
||||
_updateTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = Interval
|
||||
};
|
||||
_updateTimer.Tick += UpdateTimer_Tick;
|
||||
_updateTimer.Start();
|
||||
}
|
||||
|
||||
private void UpdateTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (IsMouseOver) return;
|
||||
PageIndex++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新页按钮
|
||||
/// </summary>
|
||||
public void UpdatePageButtons(int index = -1)
|
||||
{
|
||||
if (!CheckNull()) return;
|
||||
if (!_appliedTemplate) return;
|
||||
|
||||
var count = Items.Count;
|
||||
_widthList.Clear();
|
||||
_widthList.Add(0);
|
||||
var width = .0;
|
||||
foreach (FrameworkElement item in Items)
|
||||
{
|
||||
item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
width += item.DesiredSize.Width;
|
||||
_widthList.Add(width);
|
||||
}
|
||||
|
||||
_itemsControl.Width = _widthList.Last() + ExtendWidth;
|
||||
_panelPage.Children.Clear();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_panelPage.Children.Add(new RadioButton
|
||||
{
|
||||
Style = PageButtonStyle
|
||||
});
|
||||
}
|
||||
|
||||
if (index == -1 && count > 0) index = 0;
|
||||
if (index >= 0 && index < count)
|
||||
{
|
||||
if (_panelPage.Children[index] is RadioButton button)
|
||||
{
|
||||
button.IsChecked = true;
|
||||
button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
|
||||
UpdateItemsPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新项的位置
|
||||
/// </summary>
|
||||
private void UpdateItemsPosition()
|
||||
{
|
||||
if (!CheckNull()) return;
|
||||
if (!_appliedTemplate) return;
|
||||
if (Items.Count == 0) return;
|
||||
if (!IsCenter)
|
||||
{
|
||||
_itemsControl.BeginAnimation(MarginProperty,
|
||||
AnimationHelper.CreateAnimation(new Thickness(-_widthList[PageIndex], 0, 0, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ctl = (FrameworkElement)Items[PageIndex];
|
||||
var ctlWidth = ctl.DesiredSize.Width;
|
||||
_itemsControl.BeginAnimation(MarginProperty,
|
||||
AnimationHelper.CreateAnimation(
|
||||
new Thickness(-_widthList[PageIndex] + (ActualWidth - ctlWidth) / 2, 0, 0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||
{
|
||||
base.OnRenderSizeChanged(sizeInfo);
|
||||
UpdateItemsPosition();
|
||||
}
|
||||
|
||||
private void ButtonPages_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!CheckNull()) return;
|
||||
|
||||
_selectedButton = e.OriginalSource as RadioButton;
|
||||
|
||||
var index = _panelPage.Children.IndexOf(_selectedButton);
|
||||
if (index != -1)
|
||||
{
|
||||
PageIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonPrev_OnClick(object sender, RoutedEventArgs e) => PageIndex--;
|
||||
|
||||
private void ButtonNext_OnClick(object sender, RoutedEventArgs e) => PageIndex++;
|
||||
}
|
||||
}
|
||||
123
AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Flipper.xaml
Normal file
123
AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Flipper.xaml
Normal file
@@ -0,0 +1,123 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:AIStudio.Wpf.DiagramDesigner.Additionals.Controls"
|
||||
xmlns:converter="clr-namespace:AIStudio.Wpf.DiagramDesigner.Additionals.Converters">
|
||||
|
||||
<converter:CardClipConverter x:Key="CardClipConverter" />
|
||||
<converter:Object2VisibilityConverter x:Key="Object2VisibilityConverter"/>
|
||||
|
||||
<SineEase x:Key="FlipEase" EasingMode="EaseInOut" />
|
||||
|
||||
<Style x:Key="FlipperStyle" TargetType="{x:Type controls:Flipper}">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type controls:Flipper}">
|
||||
<Border x:Name="Border">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="FlipStates">
|
||||
<VisualStateGroup.Transitions>
|
||||
<VisualTransition From="Unflipped" To="Flipped">
|
||||
<Storyboard FillBehavior="HoldEnd">
|
||||
<DoubleAnimationUsingKeyFrames Duration="0:0:0.4" Storyboard.TargetProperty="RotationY" Storyboard.TargetName="PART_Plane3D">
|
||||
<EasingDoubleKeyFrame Value="0" KeyTime="0:0:0.0" EasingFunction="{StaticResource FlipEase}" />
|
||||
<EasingDoubleKeyFrame Value="-90" KeyTime="0:0:0.2" EasingFunction="{StaticResource FlipEase}" />
|
||||
<EasingDoubleKeyFrame Value="90" KeyTime="0:0:0.2" EasingFunction="{StaticResource FlipEase}" />
|
||||
<EasingDoubleKeyFrame Value="0" KeyTime="0:0:0.4" EasingFunction="{StaticResource FlipEase}" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BackContentPresenter">
|
||||
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" KeyTime="0:0:0.2" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="FrontContentPresenter">
|
||||
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" KeyTime="0:0:0.2" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
<VisualTransition From="Flipped" To="Unflipped">
|
||||
<Storyboard FillBehavior="HoldEnd">
|
||||
<DoubleAnimationUsingKeyFrames Duration="0:0:0.4" Storyboard.TargetProperty="RotationY" Storyboard.TargetName="PART_Plane3D">
|
||||
<EasingDoubleKeyFrame Value="0" KeyTime="0:0:0.0" EasingFunction="{StaticResource FlipEase}" />
|
||||
<EasingDoubleKeyFrame Value="90" KeyTime="0:0:0.2" EasingFunction="{StaticResource FlipEase}" />
|
||||
<EasingDoubleKeyFrame Value="-90" KeyTime="0:0:0.2" EasingFunction="{StaticResource FlipEase}" />
|
||||
<EasingDoubleKeyFrame Value="0" KeyTime="0:0:0.4" EasingFunction="{StaticResource FlipEase}" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BackContentPresenter">
|
||||
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" KeyTime="0:0:0.2" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="FrontContentPresenter">
|
||||
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" KeyTime="0:0:0.2" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
</VisualStateGroup.Transitions>
|
||||
|
||||
<VisualState x:Name="Unflipped">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BackContentPresenter">
|
||||
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="FrontContentPresenter">
|
||||
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Flipped">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BackContentPresenter">
|
||||
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="FrontContentPresenter">
|
||||
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<controls:Plane3D x:Name="PART_Plane3D" RotationY="0" ZFactor="2.055">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border x:Name="Bg" Grid.RowSpan="3" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"/>
|
||||
|
||||
<Border BorderThickness="0,0,0,1" Visibility="{TemplateBinding Header, Converter={StaticResource Object2VisibilityConverter}}" BorderBrush="{TemplateBinding BorderBrush}">
|
||||
<ContentPresenter ContentSource="Header" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" ContentStringFormat="{TemplateBinding HeaderStringFormat}"/>
|
||||
</Border>
|
||||
|
||||
<ContentPresenter Grid.Row="1" x:Name="FrontContentPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Content="{TemplateBinding FrontContent}"
|
||||
ContentTemplate="{TemplateBinding FrontContentTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding FrontContentTemplateSelector}"
|
||||
ContentStringFormat="{TemplateBinding FrontContentStringFormat}"
|
||||
RenderTransformOrigin=".5,.5" />
|
||||
<ContentPresenter Grid.Row="1" x:Name="BackContentPresenter"
|
||||
Visibility="Collapsed"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Content="{TemplateBinding BackContent}"
|
||||
ContentTemplate="{TemplateBinding BackContentTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding BackContentTemplateSelector}"
|
||||
ContentStringFormat="{TemplateBinding BackContentStringFormat}"
|
||||
RenderTransformOrigin=".5,.5" />
|
||||
|
||||
|
||||
<Border Grid.Row="2" BorderThickness="0,1,0,0" Visibility="{TemplateBinding Footer, Converter={StaticResource Object2VisibilityConverter}}" BorderBrush="{TemplateBinding BorderBrush}">
|
||||
<ContentPresenter ContentSource="Footer" ContentTemplate="{TemplateBinding FooterTemplate}" ContentTemplateSelector="{TemplateBinding FooterTemplateSelector}" ContentStringFormat="{TemplateBinding FooterStringFormat}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</controls:Plane3D>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource FlipperStyle}" TargetType="{x:Type controls:Flipper}"/>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace AIStudio.Wpf.DiagramDesigner.Additionals.Controls
|
||||
{
|
||||
[TemplatePart(Name = Plane3DPartName, Type = typeof(Plane3D))]
|
||||
[TemplateVisualState(GroupName = TemplateFlipGroupName, Name = TemplateFlippedStateName)]
|
||||
[TemplateVisualState(GroupName = TemplateFlipGroupName, Name = TemplateUnflippedStateName)]
|
||||
public class Flipper : Control
|
||||
{
|
||||
public static readonly RoutedCommand FlipCommand = new RoutedCommand();
|
||||
|
||||
public const string Plane3DPartName = "PART_Plane3D";
|
||||
public const string TemplateFlipGroupName = "FlipStates";
|
||||
public const string TemplateFlippedStateName = "Flipped";
|
||||
public const string TemplateUnflippedStateName = "Unflipped";
|
||||
|
||||
private Plane3D _plane3D;
|
||||
|
||||
static Flipper()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(Flipper), new FrameworkPropertyMetadata(typeof(Flipper)));
|
||||
}
|
||||
|
||||
public Flipper()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(FlipCommand, FlipHandler));
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FrontContentProperty = DependencyProperty.Register(
|
||||
nameof(FrontContent), typeof(object), typeof(Flipper), new PropertyMetadata(default(object)));
|
||||
|
||||
public object FrontContent
|
||||
{
|
||||
get => GetValue(FrontContentProperty);
|
||||
set => SetValue(FrontContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FrontContentTemplateProperty = DependencyProperty.Register(
|
||||
nameof(FrontContentTemplate), typeof(DataTemplate), typeof(Flipper), new PropertyMetadata(default(DataTemplate)));
|
||||
|
||||
public DataTemplate FrontContentTemplate
|
||||
{
|
||||
get => (DataTemplate)GetValue(FrontContentTemplateProperty);
|
||||
set => SetValue(FrontContentTemplateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FrontContentTemplateSelectorProperty = DependencyProperty.Register(
|
||||
nameof(FrontContentTemplateSelector), typeof(DataTemplateSelector), typeof(Flipper), new PropertyMetadata(default(DataTemplateSelector)));
|
||||
|
||||
public DataTemplateSelector FrontContentTemplateSelector
|
||||
{
|
||||
get => (DataTemplateSelector)GetValue(FrontContentTemplateSelectorProperty);
|
||||
set => SetValue(FrontContentTemplateSelectorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FrontContentStringFormatProperty = DependencyProperty.Register(
|
||||
nameof(FrontContentStringFormat), typeof(string), typeof(Flipper), new PropertyMetadata(default(string)));
|
||||
|
||||
public string FrontContentStringFormat
|
||||
{
|
||||
get => (string)GetValue(FrontContentStringFormatProperty);
|
||||
set => SetValue(FrontContentStringFormatProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BackContentProperty = DependencyProperty.Register(
|
||||
nameof(BackContent), typeof(object), typeof(Flipper), new PropertyMetadata(default(object)));
|
||||
|
||||
public object BackContent
|
||||
{
|
||||
get => GetValue(BackContentProperty);
|
||||
set => SetValue(BackContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BackContentTemplateProperty = DependencyProperty.Register(
|
||||
nameof(BackContentTemplate), typeof(DataTemplate), typeof(Flipper), new PropertyMetadata(default(DataTemplate)));
|
||||
|
||||
public DataTemplate BackContentTemplate
|
||||
{
|
||||
get => (DataTemplate)GetValue(BackContentTemplateProperty);
|
||||
set => SetValue(BackContentTemplateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BackContentTemplateSelectorProperty = DependencyProperty.Register(
|
||||
nameof(BackContentTemplateSelector), typeof(DataTemplateSelector), typeof(Flipper), new PropertyMetadata(default(DataTemplateSelector)));
|
||||
|
||||
public DataTemplateSelector BackContentTemplateSelector
|
||||
{
|
||||
get => (DataTemplateSelector)GetValue(BackContentTemplateSelectorProperty);
|
||||
set => SetValue(BackContentTemplateSelectorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BackContentStringFormatProperty = DependencyProperty.Register(
|
||||
nameof(BackContentStringFormat), typeof(string), typeof(Flipper), new PropertyMetadata(default(string)));
|
||||
|
||||
public string BackContentStringFormat
|
||||
{
|
||||
get => (string)GetValue(BackContentStringFormatProperty);
|
||||
set => SetValue(BackContentStringFormatProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsFlippedProperty = DependencyProperty.Register(
|
||||
nameof(IsFlipped), typeof(bool), typeof(Flipper), new PropertyMetadata(default(bool), IsFlippedPropertyChangedCallback));
|
||||
|
||||
private static void IsFlippedPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
||||
{
|
||||
var flipper = (Flipper)dependencyObject;
|
||||
flipper.UpdateVisualStates(true);
|
||||
flipper.RemeasureDuringFlip();
|
||||
OnIsFlippedChanged(flipper, dependencyPropertyChangedEventArgs);
|
||||
}
|
||||
|
||||
public bool IsFlipped
|
||||
{
|
||||
get => (bool)GetValue(IsFlippedProperty);
|
||||
set => SetValue(IsFlippedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent IsFlippedChangedEvent =
|
||||
EventManager.RegisterRoutedEvent(
|
||||
nameof(IsFlipped),
|
||||
RoutingStrategy.Bubble,
|
||||
typeof(RoutedPropertyChangedEventHandler<bool>),
|
||||
typeof(Flipper));
|
||||
|
||||
public event RoutedPropertyChangedEventHandler<bool> IsFlippedChanged
|
||||
{
|
||||
add => AddHandler(IsFlippedChangedEvent, value);
|
||||
remove => RemoveHandler(IsFlippedChangedEvent, value);
|
||||
}
|
||||
|
||||
private static void OnIsFlippedChanged(
|
||||
DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var instance = (Flipper)d;
|
||||
var args = new RoutedPropertyChangedEventArgs<bool>(
|
||||
(bool)e.OldValue,
|
||||
(bool)e.NewValue)
|
||||
{
|
||||
RoutedEvent = IsFlippedChangedEvent
|
||||
};
|
||||
instance.RaiseEvent(args);
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
UpdateVisualStates(false);
|
||||
|
||||
_plane3D = GetTemplateChild(Plane3DPartName) as Plane3D;
|
||||
}
|
||||
|
||||
private void RemeasureDuringFlip()
|
||||
{
|
||||
//not entirely happy hardcoding this, but I have explored other options I am not happy with, and this will do for now
|
||||
const int storyboardMs = 400;
|
||||
const int granularity = 6;
|
||||
|
||||
var remeasureInterval = new TimeSpan(0, 0, 0, 0, storyboardMs / granularity);
|
||||
var refreshCount = 0;
|
||||
var plane3D = _plane3D;
|
||||
if (plane3D is null) return;
|
||||
|
||||
DispatcherTimer dt = null;
|
||||
dt = new DispatcherTimer(remeasureInterval, DispatcherPriority.Normal,
|
||||
(sender, args) => {
|
||||
plane3D.InvalidateMeasure();
|
||||
if (refreshCount++ == granularity)
|
||||
dt?.Stop();
|
||||
}, Dispatcher);
|
||||
dt.Start();
|
||||
}
|
||||
|
||||
private void UpdateVisualStates(bool useTransitions)
|
||||
{
|
||||
VisualStateManager.GoToState(this, IsFlipped ? TemplateFlippedStateName : TemplateUnflippedStateName,
|
||||
useTransitions);
|
||||
}
|
||||
|
||||
private void FlipHandler(object sender, ExecutedRoutedEventArgs executedRoutedEventArgs)
|
||||
{
|
||||
SetCurrentValue(IsFlippedProperty, !IsFlipped);
|
||||
}
|
||||
|
||||
|
||||
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
|
||||
nameof(Header), typeof(object), typeof(Flipper), new PropertyMetadata(default(object)));
|
||||
|
||||
public object Header
|
||||
{
|
||||
get => GetValue(HeaderProperty);
|
||||
set => SetValue(HeaderProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register(
|
||||
nameof(HeaderTemplate), typeof(DataTemplate), typeof(Flipper), new PropertyMetadata(default(DataTemplate)));
|
||||
|
||||
[Bindable(true), Category("Content")]
|
||||
public DataTemplate HeaderTemplate
|
||||
{
|
||||
get => (DataTemplate)GetValue(HeaderTemplateProperty);
|
||||
set => SetValue(HeaderTemplateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty HeaderTemplateSelectorProperty = DependencyProperty.Register(
|
||||
nameof(HeaderTemplateSelector), typeof(DataTemplateSelector), typeof(Flipper), new PropertyMetadata(default(DataTemplateSelector)));
|
||||
|
||||
[Bindable(true), Category("Content")]
|
||||
public DataTemplateSelector HeaderTemplateSelector
|
||||
{
|
||||
get => (DataTemplateSelector)GetValue(HeaderTemplateSelectorProperty);
|
||||
set => SetValue(HeaderTemplateSelectorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty HeaderStringFormatProperty = DependencyProperty.Register(
|
||||
nameof(HeaderStringFormat), typeof(string), typeof(Flipper), new PropertyMetadata(default(string)));
|
||||
|
||||
[Bindable(true), Category("Content")]
|
||||
public string HeaderStringFormat
|
||||
{
|
||||
get => (string)GetValue(HeaderStringFormatProperty);
|
||||
set => SetValue(HeaderStringFormatProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(
|
||||
nameof(Footer), typeof(object), typeof(Flipper), new PropertyMetadata(default(object)));
|
||||
|
||||
public object Footer
|
||||
{
|
||||
get => GetValue(FooterProperty);
|
||||
set => SetValue(FooterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FooterTemplateProperty = DependencyProperty.Register(
|
||||
nameof(FooterTemplate), typeof(DataTemplate), typeof(Flipper), new PropertyMetadata(default(DataTemplate)));
|
||||
|
||||
[Bindable(true), Category("Content")]
|
||||
public DataTemplate FooterTemplate
|
||||
{
|
||||
get => (DataTemplate)GetValue(FooterTemplateProperty);
|
||||
set => SetValue(FooterTemplateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FooterTemplateSelectorProperty = DependencyProperty.Register(
|
||||
nameof(FooterTemplateSelector), typeof(DataTemplateSelector), typeof(Flipper), new PropertyMetadata(default(DataTemplateSelector)));
|
||||
|
||||
[Bindable(true), Category("Content")]
|
||||
public DataTemplateSelector FooterTemplateSelector
|
||||
{
|
||||
get => (DataTemplateSelector)GetValue(FooterTemplateSelectorProperty);
|
||||
set => SetValue(FooterTemplateSelectorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FooterStringFormatProperty = DependencyProperty.Register(
|
||||
nameof(FooterStringFormat), typeof(string), typeof(Flipper), new PropertyMetadata(default(string)));
|
||||
|
||||
[Bindable(true), Category("Content")]
|
||||
public string FooterStringFormat
|
||||
{
|
||||
get => (string)GetValue(FooterStringFormatProperty);
|
||||
set => SetValue(FooterStringFormatProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
259
AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Plane3D.cs
Normal file
259
AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Plane3D.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Media3D;
|
||||
|
||||
namespace AIStudio.Wpf.DiagramDesigner.Additionals.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// View a control on a 3D plane.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Taken from http://blogs.msdn.com/greg_schechter/archive/2007/10/26/enter-the-planerator-dead-simple-3d-in-wpf-with-a-stupid-name.aspx , Greg Schechter - Fall 2007
|
||||
/// </remarks>
|
||||
[ContentProperty("Child")]
|
||||
public class Plane3D : FrameworkElement
|
||||
{
|
||||
private FrameworkElement _logicalChild;
|
||||
private FrameworkElement _visualChild;
|
||||
private FrameworkElement _originalChild;
|
||||
|
||||
private readonly QuaternionRotation3D _quaternionRotation = new QuaternionRotation3D();
|
||||
private readonly RotateTransform3D _rotationTransform = new RotateTransform3D();
|
||||
private Viewport3D _viewport3D;
|
||||
private readonly ScaleTransform3D _scaleTransform = new ScaleTransform3D();
|
||||
|
||||
private static readonly Point3D[] Mesh = { new Point3D(0, 0, 0), new Point3D(0, 1, 0), new Point3D(1, 1, 0), new Point3D(1, 0, 0) };
|
||||
private static readonly Point[] TexCoords = { new Point(0, 1), new Point(0, 0), new Point(1, 0), new Point(1, 1) };
|
||||
private static readonly int[] Indices = { 0, 2, 1, 0, 3, 2 };
|
||||
private static readonly Vector3D XAxis = new Vector3D(1, 0, 0);
|
||||
private static readonly Vector3D YAxis = new Vector3D(0, 1, 0);
|
||||
private static readonly Vector3D ZAxis = new Vector3D(0, 0, 1);
|
||||
|
||||
|
||||
public static readonly DependencyProperty RotationXProperty =
|
||||
DependencyProperty.Register("RotationX", typeof(double), typeof(Plane3D), new UIPropertyMetadata(0.0, (d, args) => ((Plane3D)d).UpdateRotation()));
|
||||
|
||||
public double RotationX
|
||||
{
|
||||
get => (double)GetValue(RotationXProperty);
|
||||
set => SetValue(RotationXProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty RotationYProperty =
|
||||
DependencyProperty.Register("RotationY", typeof(double), typeof(Plane3D), new UIPropertyMetadata(0.0, (d, args) => ((Plane3D)d).UpdateRotation()));
|
||||
|
||||
public double RotationY
|
||||
{
|
||||
get => (double)GetValue(RotationYProperty);
|
||||
set => SetValue(RotationYProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty RotationZProperty =
|
||||
DependencyProperty.Register("RotationZ", typeof(double), typeof(Plane3D), new UIPropertyMetadata(0.0, (d, args) => ((Plane3D)d).UpdateRotation()));
|
||||
|
||||
public double RotationZ
|
||||
{
|
||||
get => (double)GetValue(RotationZProperty);
|
||||
set => SetValue(RotationZProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FieldOfViewProperty =
|
||||
DependencyProperty.Register("FieldOfView", typeof(double), typeof(Plane3D), new UIPropertyMetadata(45.0, (d, args) => ((Plane3D)d).Update3D(),
|
||||
(d, val) => Math.Min(Math.Max((double)val, 0.5), 179.9))); // clamp to a meaningful range
|
||||
|
||||
public double FieldOfView
|
||||
{
|
||||
get => (double)GetValue(FieldOfViewProperty);
|
||||
set => SetValue(FieldOfViewProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ZFactorProperty = DependencyProperty.Register(
|
||||
"ZFactor", typeof(double), typeof(Plane3D), new UIPropertyMetadata(2.0, (d, args) => ((Plane3D)d).UpdateRotation()));
|
||||
|
||||
public double ZFactor
|
||||
{
|
||||
get => (double)GetValue(ZFactorProperty);
|
||||
set => SetValue(ZFactorProperty, value);
|
||||
}
|
||||
|
||||
public FrameworkElement Child
|
||||
{
|
||||
get => _originalChild;
|
||||
set
|
||||
{
|
||||
if (Equals(_originalChild, value)) return;
|
||||
RemoveVisualChild(_visualChild);
|
||||
RemoveLogicalChild(_logicalChild);
|
||||
|
||||
// Wrap child with special decorator that catches layout invalidations.
|
||||
_originalChild = value;
|
||||
_logicalChild = new LayoutInvalidationCatcher() { Child = _originalChild };
|
||||
_visualChild = CreateVisualChild();
|
||||
|
||||
AddVisualChild(_visualChild);
|
||||
|
||||
// Need to use a logical child here to make sure databinding operations get down to it,
|
||||
// since otherwise the child appears only as the Visual to a Viewport2DVisual3D, which
|
||||
// doesn't have databinding operations pass into it from above.
|
||||
AddLogicalChild(_logicalChild);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
Size result;
|
||||
if (_logicalChild != null)
|
||||
{
|
||||
// Measure based on the size of the logical child, since we want to align with it.
|
||||
_logicalChild.Measure(availableSize);
|
||||
result = _logicalChild.DesiredSize;
|
||||
_visualChild?.Measure(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new Size(0, 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
if (_logicalChild is null) return base.ArrangeOverride(finalSize);
|
||||
_logicalChild.Arrange(new Rect(finalSize));
|
||||
_visualChild?.Arrange(new Rect(finalSize));
|
||||
Update3D();
|
||||
return base.ArrangeOverride(finalSize);
|
||||
}
|
||||
|
||||
protected override Visual GetVisualChild(int index) => _visualChild;
|
||||
|
||||
protected override int VisualChildrenCount => _visualChild == null ? 0 : 1;
|
||||
|
||||
private FrameworkElement CreateVisualChild()
|
||||
{
|
||||
var simpleQuad = new MeshGeometry3D
|
||||
{
|
||||
Positions = new Point3DCollection(Mesh),
|
||||
TextureCoordinates = new PointCollection(TexCoords),
|
||||
TriangleIndices = new Int32Collection(Indices)
|
||||
};
|
||||
|
||||
// Front material is interactive, back material is not.
|
||||
Material frontMaterial = new DiffuseMaterial(Brushes.White);
|
||||
frontMaterial.SetValue(Viewport2DVisual3D.IsVisualHostMaterialProperty, true);
|
||||
|
||||
var vb = new VisualBrush(_logicalChild);
|
||||
SetCachingForObject(vb); // big perf wins by caching!!
|
||||
Material backMaterial = new DiffuseMaterial(vb);
|
||||
|
||||
_rotationTransform.Rotation = _quaternionRotation;
|
||||
var xfGroup = new Transform3DGroup { Children = { _scaleTransform, _rotationTransform } };
|
||||
|
||||
var backModel = new GeometryModel3D { Geometry = simpleQuad, Transform = xfGroup, BackMaterial = backMaterial };
|
||||
var m3DGroup = new Model3DGroup
|
||||
{
|
||||
Children = { new DirectionalLight(Colors.White, new Vector3D(0, 0, -1)),
|
||||
new DirectionalLight(Colors.White, new Vector3D(0.1, -0.1, 1)),
|
||||
backModel }
|
||||
};
|
||||
|
||||
// Non-interactive Visual3D consisting of the backside, and two lights.
|
||||
var mv3D = new ModelVisual3D { Content = m3DGroup };
|
||||
|
||||
// Interactive frontside Visual3D
|
||||
var frontModel = new Viewport2DVisual3D { Geometry = simpleQuad, Visual = _logicalChild, Material = frontMaterial, Transform = xfGroup };
|
||||
|
||||
// Cache the brush in the VP2V3 by setting caching on it. Big perf wins.
|
||||
SetCachingForObject(frontModel);
|
||||
|
||||
// Scene consists of both the above Visual3D's.
|
||||
_viewport3D = new Viewport3D { ClipToBounds = false, Children = { mv3D, frontModel } };
|
||||
|
||||
UpdateRotation();
|
||||
|
||||
return _viewport3D;
|
||||
}
|
||||
|
||||
private void SetCachingForObject(DependencyObject d)
|
||||
{
|
||||
RenderOptions.SetCachingHint(d, CachingHint.Cache);
|
||||
RenderOptions.SetCacheInvalidationThresholdMinimum(d, 0.5);
|
||||
RenderOptions.SetCacheInvalidationThresholdMaximum(d, 2.0);
|
||||
}
|
||||
|
||||
private void UpdateRotation()
|
||||
{
|
||||
var qx = new Quaternion(XAxis, RotationX);
|
||||
var qy = new Quaternion(YAxis, RotationY);
|
||||
var qz = new Quaternion(ZAxis, RotationZ);
|
||||
|
||||
_quaternionRotation.Quaternion = qx * qy * qz;
|
||||
}
|
||||
|
||||
private void Update3D()
|
||||
{
|
||||
// Use GetDescendantBounds for sizing and centering since DesiredSize includes layout whitespace, whereas GetDescendantBounds
|
||||
// is tighter
|
||||
var logicalBounds = VisualTreeHelper.GetDescendantBounds(_logicalChild);
|
||||
var w = logicalBounds.Width;
|
||||
var h = logicalBounds.Height;
|
||||
|
||||
// Create a camera that looks down -Z, with up as Y, and positioned right halfway in X and Y on the element,
|
||||
// and back along Z the right distance based on the field-of-view is the same projected size as the 2D content
|
||||
// that it's looking at. See http://blogs.msdn.com/greg_schechter/archive/2007/04/03/camera-construction-in-parallaxui.aspx
|
||||
// for derivation of this camera.
|
||||
var fovInRadians = FieldOfView * (Math.PI / 180);
|
||||
var zValue = w / Math.Tan(fovInRadians / 2) / ZFactor;
|
||||
if (_viewport3D != null)
|
||||
{
|
||||
_viewport3D.Camera = new PerspectiveCamera(new Point3D(w / 2, h / 2, zValue),
|
||||
-ZAxis,
|
||||
YAxis,
|
||||
FieldOfView);
|
||||
}
|
||||
|
||||
_scaleTransform.ScaleX = w;
|
||||
_scaleTransform.ScaleY = h;
|
||||
_rotationTransform.CenterX = w / 2;
|
||||
_rotationTransform.CenterY = h / 2;
|
||||
}
|
||||
|
||||
#region Private Classes
|
||||
|
||||
/// <summary>
|
||||
/// Wrap this around a class that we want to catch the measure and arrange
|
||||
/// processes occuring on, and propagate to the parent Planerator, if any.
|
||||
/// Do this because layout invalidations don't flow up out of a
|
||||
/// Viewport2DVisual3D object.
|
||||
/// </summary>
|
||||
private class LayoutInvalidationCatcher : Decorator
|
||||
{
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
Plane3D pl = Parent as Plane3D;
|
||||
if (pl != null)
|
||||
{
|
||||
pl.InvalidateMeasure();
|
||||
}
|
||||
return base.MeasureOverride(constraint);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size arrangeSize)
|
||||
{
|
||||
Plane3D pl = Parent as Plane3D;
|
||||
if (pl != null)
|
||||
{
|
||||
pl.InvalidateArrange();
|
||||
}
|
||||
return base.ArrangeOverride(arrangeSize);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace AIStudio.Wpf.DiagramDesigner.Additionals.Converters
|
||||
{
|
||||
public class CardClipConverter : IMultiValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 1 - Content presenter render size,
|
||||
/// 2 - Clipping border padding (main control padding)
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length != 2 || !(values[0] is Size) || !(values[1] is Thickness))
|
||||
return Binding.DoNothing;
|
||||
|
||||
var size = (Size)values[0];
|
||||
var farPoint = new Point(
|
||||
Math.Max(0, size.Width),
|
||||
Math.Max(0, size.Height));
|
||||
var padding = (Thickness)values[1];
|
||||
farPoint.Offset(padding.Left + padding.Right, padding.Top + padding.Bottom);
|
||||
|
||||
return new Rect(
|
||||
new Point(),
|
||||
new Point(farPoint.X, farPoint.Y));
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace AIStudio.Wpf.DiagramDesigner.Additionals.Converters
|
||||
{
|
||||
public class Object2VisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value == null ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/AIStudio.Wpf.DiagramDesigner.Additionals;component/Controls/Flipper.xaml" />
|
||||
<ResourceDictionary Source="/AIStudio.Wpf.DiagramDesigner.Additionals;component/Controls/Carousel.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
Reference in New Issue
Block a user