Skip to content

Commit

Permalink
Merge pull request stride3d#1014 from cstdbool/961_datetime_cannot_be…
Browse files Browse the repository at this point in the history
…_serialized

[Editor] Add DateTime property editor and improve TimeSpan property editor
  • Loading branch information
xen2 authored Feb 8, 2021
2 parents 1548c8c + bd19288 commit 765654a
Show file tree
Hide file tree
Showing 9 changed files with 672 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public AssetNodeContainer()
NodeBuilder.RegisterPrimitiveType(typeof(IReference));
NodeBuilder.RegisterPrimitiveType(typeof(PropertyKey));
NodeBuilder.RegisterPrimitiveType(typeof(TimeSpan));
NodeBuilder.RegisterPrimitiveType(typeof(DateTime));
NodeBuilder.RegisterPrimitiveType(typeof(Guid));
NodeBuilder.RegisterPrimitiveType(typeof(AssetId));
NodeBuilder.RegisterPrimitiveType(typeof(Color));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -807,29 +807,35 @@
<!-- Provider for time span editor -->
<view:TypeMatchTemplateProvider x:Key="TimeSpanPropertyTemplateProvider" Type="{x:Type s:TimeSpan}" edvw:PropertyViewHelper.TemplateCategory="PropertyEditor">
<DataTemplate DataType="qvm:NodeViewModel">
<!-- Note: timespan are displayed in seconds -->
<sd:NumericTextBox x:Name="TextBox" SelectAllOnFocus="True" Margin="2"
Value="{Binding NodeValue, Converter={sd:Chained {cvt:DifferentValuesToNull}, {sd:TimeSpanToDouble}}}"
Minimum="{Binding Minimum, Converter={sd:Chained {cvt:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue=0}"
Maximum="{Binding Maximum, Converter={sd:Chained {cvt:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue={x:Static s:Double.MaxValue}}"
SmallChange="{Binding SmallStep, Converter={sd:Chained {cvt:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue=0.1}"
LargeChange="{Binding LargeStep, Converter={sd:Chained {cvt:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue=1}"
DecimalPlaces="{Binding DecimalPlaces, Converter={sd:Chained {cvt:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue=3}"
WatermarkContentTemplate="{StaticResource DifferentValuesWatermarkTemplate}"
DisplayUpDownButtons="False">
<i:Interaction.Behaviors>
<!--<sd:NumericTextBoxTransactionalRepeatButtonsBehavior UndoRedoService="{Binding Session.UndoRedoService}"/>-->
<behaviors:TextBoxPropertyValueValidationBehavior AdornerStoryboard="{StaticResource HighlightBorderAdornerValidationErrorStoryboard}"/>
</i:Interaction.Behaviors>
</sd:NumericTextBox>
<sd:TimeSpanEditor x:Name="TimeSpanEditor" Margin="2"
Value="{Binding NodeValue, Converter={cvt:DifferentValuesToNull}}"
WatermarkContentTemplate="{StaticResource DifferentValuesWatermarkTemplate}">
</sd:TimeSpanEditor>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding NodeValue}" Value="{x:Static qvm:NodeViewModel.DifferentValues}">
<Setter TargetName="TextBox" Property="WatermarkContent" Value="{sd:Localize (Different values)}"/>
<Setter TargetName="TimeSpanEditor" Property="WatermarkContent" Value="×"/>
<Setter TargetName="TimeSpanEditor" Property="IsEnabled" Value="False"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</view:TypeMatchTemplateProvider>

<!-- Provider for date time editor -->
<view:TypeMatchTemplateProvider x:Key="DateTimePropertyTemplateProvider" Type="{x:Type s:DateTime}" edvw:PropertyViewHelper.TemplateCategory="PropertyEditor">
<DataTemplate DataType="qvm:NodeViewModel">
<sd:DateTimeEditor x:Name="DateTimeEditor" Margin="2"
Value="{Binding NodeValue, Converter={cvt:DifferentValuesToNull}}"
WatermarkContentTemplate="{StaticResource DifferentValuesWatermarkTemplate}">
</sd:DateTimeEditor>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding NodeValue}" Value="{x:Static qvm:NodeViewModel.DifferentValues}">
<Setter TargetName="DateTimeEditor" Property="WatermarkContent" Value="{sd:Localize (Different values)}"/>
<Setter TargetName="DateTimeEditor" Property="IsEnabled" Value="False"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</view:TypeMatchTemplateProvider>

<!-- Provider for string editor -->
<view:TypeMatchTemplateProvider x:Key="StringPropertyTemplateProvider" Type="{x:Type s:String}" edvw:PropertyViewHelper.TemplateCategory="PropertyEditor">
<DataTemplate DataType="qvm:NodeViewModel">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
// Copyright (c) Stride contributors (https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Stride.Core.Presentation.Internal;

namespace Stride.Core.Presentation.Controls
{
public sealed class DateTimeEditor : Control
{
private bool interlock;
private bool templateApplied;
private DependencyProperty initializingProperty;

/// <summary>
/// Identifies the <see cref="IsDropDownOpen"/> dependency property.
/// </summary>
public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(DateTimeEditor), new PropertyMetadata(BooleanBoxes.FalseBox));

/// <summary>
/// Identifies the <see cref="WatermarkContent"/> dependency property.
/// </summary>
public static readonly DependencyProperty WatermarkContentProperty = DependencyProperty.Register("WatermarkContent", typeof(object), typeof(DateTimeEditor), new PropertyMetadata(null));

/// <summary>
/// Identifies the <see cref="WatermarkContentTemplate"/> dependency property.
/// </summary>
public static readonly DependencyProperty WatermarkContentTemplateProperty = DependencyProperty.Register("WatermarkContentTemplate", typeof(DataTemplate), typeof(DateTimeEditor), new PropertyMetadata(null));

/// <summary>
/// Identifies the <see cref="Value"/> dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(DateTime?), typeof(DateTimeEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValuePropertyChanged, null, false, UpdateSourceTrigger.Explicit));

/// <summary>
/// Identifies the <see cref="Year"/> dependency property.
/// </summary>
public static readonly DependencyProperty YearProperty = DependencyProperty.Register("Year", typeof(int?), typeof(DateTimeEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnComponentPropertyChanged));

/// <summary>
/// Identifies the <see cref="Month"/> dependency property.
/// </summary>
public static readonly DependencyProperty MonthProperty = DependencyProperty.Register("Month", typeof(int?), typeof(DateTimeEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnComponentPropertyChanged));

/// <summary>
/// Identifies the <see cref="Day"/> dependency property.
/// </summary>
public static readonly DependencyProperty DayProperty = DependencyProperty.Register("Day", typeof(int?), typeof(DateTimeEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnComponentPropertyChanged));

/// <summary>
/// Identifies the <see cref="Hour"/> dependency property.
/// </summary>
public static readonly DependencyProperty HourProperty = DependencyProperty.Register("Hour", typeof(int?), typeof(DateTimeEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnComponentPropertyChanged));

/// <summary>
/// Identifies the <see cref="Minute"/> dependency property.
/// </summary>
public static readonly DependencyProperty MinuteProperty = DependencyProperty.Register("Minute", typeof(int?), typeof(DateTimeEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnComponentPropertyChanged));

/// <summary>
/// Identifies the <see cref="Second"/> dependency property.
/// </summary>
public static readonly DependencyProperty SecondProperty = DependencyProperty.Register("Second", typeof(double?), typeof(DateTimeEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnComponentPropertyChanged));

/// <summary>
/// Gets or sets whether the drop-down of this control editor is currently open.
/// </summary>
public bool IsDropDownOpen { get { return (bool)GetValue(IsDropDownOpenProperty); } set { SetValue(IsDropDownOpenProperty, value.Box()); } }

/// <summary>
/// Gets or sets the content to display when the TextBox is empty.
/// </summary>
public object WatermarkContent { get { return GetValue(WatermarkContentProperty); } set { SetValue(WatermarkContentProperty, value); } }

/// <summary>
/// Gets or sets the template of the content to display when the TextBox is empty.
/// </summary>
public DataTemplate WatermarkContentTemplate { get { return (DataTemplate)GetValue(WatermarkContentTemplateProperty); } set { SetValue(WatermarkContentTemplateProperty, value); } }

/// <summary>
/// Gets or sets the date time associated to this control.
/// </summary>
public DateTime? Value { get { return (DateTime?)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }

/// <summary>
/// Gets or sets the year displayed in the <see cref="DateTimeEditor"/>.
/// </summary>
public int? Year { get { return (int?)GetValue(YearProperty); } set { SetValue(YearProperty, value); } }

/// <summary>
/// Gets or sets the month displayed in the <see cref="DateTimeEditor"/>.
/// </summary>
public int? Month { get { return (int?)GetValue(MonthProperty); } set { SetValue(MonthProperty, value); } }

/// <summary>
/// Gets or sets the day displayed in the <see cref="DateTimeEditor"/>.
/// </summary>
public int? Day { get { return (int?)GetValue(DayProperty); } set { SetValue(DayProperty, value); } }

/// <summary>
/// Gets or sets the hour displayed in the <see cref="DateTimeEditor"/>.
/// </summary>
public int? Hour { get { return (int?)GetValue(HourProperty); } set { SetValue(HourProperty, value); } }

/// <summary>
/// Gets or sets the minute displayed in the <see cref="DateTimeEditor"/>.
/// </summary>
public int? Minute { get { return (int?)GetValue(MinuteProperty); } set { SetValue(MinuteProperty, value); } }

/// <summary>
/// Gets or sets the second displayed in the <see cref="DateTimeEditor"/>.
/// </summary>
public double? Second { get { return (double?)GetValue(SecondProperty); } set { SetValue(SecondProperty, value); } }

/// <inheritdoc/>
public override void OnApplyTemplate()
{
templateApplied = false;
base.OnApplyTemplate();
templateApplied = true;
}

/// <inheritdoc/>
protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
{
base.OnIsKeyboardFocusWithinChanged(e);
if (IsDropDownOpen && !IsKeyboardFocusWithin)
{
SetCurrentValue(IsDropDownOpenProperty, false);
}
}

/// <summary>
/// Updates the properties corresponding to the components of the date time from the given date time value.
/// </summary>
/// <param name="value">The date time from which to update component properties.</param>
private void UpdateComponentsFromValue(DateTime? value)
{
if (value != null)
{
SetCurrentValue(YearProperty, value.Value.Year);
SetCurrentValue(MonthProperty, value.Value.Month);
SetCurrentValue(DayProperty, value.Value.Day);
SetCurrentValue(HourProperty, value.Value.Hour);
SetCurrentValue(MinuteProperty, value.Value.Minute);
SetCurrentValue(SecondProperty, (double)(value.Value.Ticks % TimeSpan.TicksPerMinute) / TimeSpan.TicksPerSecond);
}
}

/// <summary>
/// Updates the <see cref="Value"/> property according to a change in the given component property.
/// </summary>
/// <param name="property">The component property from which to update the <see cref="Value"/>.</param>
private DateTime? UpdateValueFromComponent(DependencyProperty property)
{
// NOTE: Precision must be on OS tick level.

if (property == YearProperty)
{
if (!Year.HasValue || !Value.HasValue)
return null;
long ticks = new DateTime(Year.Value, Value.Value.Month, Math.Min(DateTime.DaysInMonth(Year.Value, Value.Value.Month), Value.Value.Day), Value.Value.Hour, Value.Value.Minute, 0).Ticks;
return new DateTime(ticks + Value.Value.Ticks % TimeSpan.TicksPerMinute);
}

if (property == MonthProperty)
{
if (!Month.HasValue || !Value.HasValue)
return null;
long ticks = new DateTime(Value.Value.Year, Month.Value, Math.Min(DateTime.DaysInMonth(Value.Value.Year, Month.Value), Value.Value.Day), Value.Value.Hour, Value.Value.Minute, 0).Ticks;
return new DateTime(ticks + Value.Value.Ticks % TimeSpan.TicksPerMinute);
}

if (property == DayProperty)
{
if (!Day.HasValue || !Value.HasValue)
return null;
long ticks = new DateTime(Value.Value.Year, Value.Value.Month, Math.Min(DateTime.DaysInMonth(Value.Value.Year, Value.Value.Month), Day.Value), Value.Value.Hour, Value.Value.Minute, 0).Ticks;
return new DateTime(ticks + Value.Value.Ticks % TimeSpan.TicksPerMinute);
}

if (property == HourProperty)
{
if (!Hour.HasValue || !Value.HasValue)
return null;
long ticks = new DateTime(Value.Value.Year, Value.Value.Month, Value.Value.Day, Hour.Value, Value.Value.Minute, 0).Ticks;
return new DateTime(ticks + Value.Value.Ticks % TimeSpan.TicksPerMinute);
}

if (property == MinuteProperty)
{
if (!Minute.HasValue || !Value.HasValue)
return null;
long ticks = new DateTime(Value.Value.Year, Value.Value.Month, Value.Value.Day, Value.Value.Hour, Minute.Value, 0).Ticks;
return new DateTime(ticks + Value.Value.Ticks % TimeSpan.TicksPerMinute);
}

if (property == SecondProperty)
{
if (!Second.HasValue || !Value.HasValue)
return null;
long ticks = Value.Value.Ticks - (Value.Value.Ticks % TimeSpan.TicksPerMinute);
return new DateTime(ticks + (long)(Second.Value * TimeSpan.TicksPerSecond));
}

throw new ArgumentException("Property unsupported by method UpdateValueFromComponent.");
}

/// <summary>
/// Raised when the <see cref="Value"/> property is modified.
/// </summary>
private void OnValueValueChanged()
{
var isInitializing = !templateApplied && initializingProperty == null;
if (isInitializing)
initializingProperty = ValueProperty;

if (!interlock)
{
interlock = true;
UpdateComponentsFromValue(Value);
interlock = false;
}

UpdateBinding(ValueProperty);
if (isInitializing)
initializingProperty = null;
}

/// <summary>
/// Raised when either of the <see cref="Year"/>, <see cref="Month"/>, <see cref="Day"/>, <see cref="Hour"/>, <see cref="Minute"/> or <see cref="Second"/> properties are modified.
/// </summary>
/// <param name="e">The event data.</param>
private void OnComponentPropertyChanged(DependencyPropertyChangedEventArgs e)
{
var isInitializing = !templateApplied && initializingProperty == null;
if (isInitializing)
initializingProperty = e.Property;

if (!interlock)
{
interlock = true;
Value = UpdateValueFromComponent(e.Property);
UpdateComponentsFromValue(Value);
interlock = false;
}

UpdateBinding(e.Property);
if (isInitializing)
initializingProperty = null;
}

/// <summary>
/// Updates the binding of the given dependency property.
/// </summary>
/// <param name="dependencyProperty">The dependency property.</param>
private void UpdateBinding(DependencyProperty dependencyProperty)
{
if (dependencyProperty != initializingProperty)
{
var expression = GetBindingExpression(dependencyProperty);
expression?.UpdateSource();
}
}

/// <summary>
/// Raised by <see cref="YearProperty"/>, <see cref="MonthProperty"/>, <see cref="DayProperty"/>, <see cref="HourProperty"/>, <see cref="MinuteProperty"/> or <see cref="SecondProperty"/> when the <see cref="Year"/>, <see cref="Month"/>, <see cref="Day"/>, <see cref="Hour"/>, <see cref="Minute"/> or <see cref="Second"/> dependency property is modified.
/// </summary>
/// <param name="sender">The dependency object where the event handler is attached.</param>
/// <param name="e">The event data.</param>
private static void OnComponentPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var editor = (DateTimeEditor)sender;
editor.OnComponentPropertyChanged(e);
}

/// <summary>
/// Raised by <see cref="ValueProperty"/> when the <see cref="Value"/> dependency property is modified.
/// </summary>
/// <param name="sender">The dependency object where the event handler is attached.</param>
/// <param name="e">The event data.</param>
private static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var editor = (DateTimeEditor)sender;
editor.OnValueValueChanged();
}
}
}
Loading

0 comments on commit 765654a

Please sign in to comment.