页面视图新增缩略图模式

This commit is contained in:
艾竹
2023-05-27 12:35:44 +08:00
parent b11d39024a
commit 01131dde47
25 changed files with 2177 additions and 190 deletions

View 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>

View File

@@ -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++;
}
}

View 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>

View File

@@ -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);
}
}
}

View 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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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>