diff options
author | Bertrand Le Roy <beleroy@microsoft.com> | 2018-01-13 00:59:03 +0300 |
---|---|---|
committer | Bertrand Le Roy <beleroy@microsoft.com> | 2018-01-13 00:59:03 +0300 |
commit | 1454871225d3e69cf94e33270bbabb0abb9a87ff (patch) | |
tree | f0bdedd599af2e52cbba2aa33eecb141088d879c | |
parent | 3209d96cb4db998373c2d7876a33b423d860eed0 (diff) |
Allow for properties with subproperties, and implement it on Brush opacity, color space, and color.bleroy-subproperties-archived
-- Note: we decided against this implementation, this branch is only for reference.
15 files changed, 389 insertions, 104 deletions
diff --git a/Xamarin.PropertyEditing.Tests/ComplexPropertyViewModelTests.cs b/Xamarin.PropertyEditing.Tests/ComplexPropertyViewModelTests.cs new file mode 100644 index 0000000..1036128 --- /dev/null +++ b/Xamarin.PropertyEditing.Tests/ComplexPropertyViewModelTests.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; +using Xamarin.PropertyEditing.Tests.MockPropertyInfo; +using System.Threading.Tasks; + +namespace Xamarin.PropertyEditing.Tests +{ + internal class ComplexPropertyViewModelTests + { + [Test] + public async Task SubPropertyCanBeEdited () + { + var property = new MockComplexPropertyInfo<int> ("Property"); + MockSubPropertyInfo<double> subProperty = property.AddSubProperty<double> ("SubProperty"); + var editor = new MockObjectEditor (property); + + var changed = false; + editor.PropertyChanged += (s, e) => { + if (e.Property == subProperty) { + changed = true; + } + }; + Assert.AreEqual (default (double), (await editor.GetValueAsync<double> (subProperty)).Value); + await editor.SetValueAsync (subProperty, new ValueInfo<double> { Source = ValueSource.Local, Value = 1.0 }); + Assert.IsTrue (changed); + Assert.AreEqual (1.0, (await editor.GetValueAsync<double> (subProperty)).Value); + } + } +} diff --git a/Xamarin.PropertyEditing.Tests/MockObjectEditor.cs b/Xamarin.PropertyEditing.Tests/MockObjectEditor.cs index 714f527..28fb0c3 100644 --- a/Xamarin.PropertyEditing.Tests/MockObjectEditor.cs +++ b/Xamarin.PropertyEditing.Tests/MockObjectEditor.cs @@ -15,7 +15,11 @@ namespace Xamarin.PropertyEditing.Tests public MockObjectEditor (params IPropertyInfo[] properties) { - Properties = properties.ToArray (); + Properties = properties + .Union(properties + .OfType<IComplexPropertyInfo>() + .SelectMany(p => p.Properties) + ).ToArray (); } public MockObjectEditor (MockControl control) @@ -99,8 +103,7 @@ namespace Xamarin.PropertyEditing.Tests public Task<IReadOnlyList<string>> GetHandlersAsync (IEventInfo ev) { - string handler; - if (this.events.TryGetValue (ev, out handler)) + if (this.events.TryGetValue (ev, out string handler)) return Task.FromResult<IReadOnlyList<string>> (new[] { handler }); return Task.FromResult<IReadOnlyList<string>> (new string[0]); @@ -119,11 +122,8 @@ namespace Xamarin.PropertyEditing.Tests object softValue = value; if (typeof(T) != property.Type) { - IPropertyConverter converter = property as IPropertyConverter; - - object v; - if (converter != null && converter.TryConvert (value.Value, property.Type, out v)) { - var softType = typeof(ValueInfo<>).MakeGenericType (property.Type); + if (property is IPropertyConverter converter && converter.TryConvert (value.Value, property.Type, out object v)) { + Type softType = typeof (ValueInfo<>).MakeGenericType (property.Type); softValue = Activator.CreateInstance (softType); softType.GetProperty ("Value").SetValue (softValue, v); softType.GetProperty ("Source").SetValue (softValue, value.Source); @@ -139,8 +139,7 @@ namespace Xamarin.PropertyEditing.Tests if (variation != null) throw new NotSupportedException (); // TODO - object value; - if (this.values.TryGetValue (property, out value)) { + if (this.values.TryGetValue (property, out object value)) { var info = value as ValueInfo<T>; if (info != null) return info; @@ -152,14 +151,12 @@ namespace Xamarin.PropertyEditing.Tests } else { ValueSource source = ValueSource.Local; Type valueType = value.GetType (); - if (valueType.IsConstructedGenericType && valueType.GetGenericTypeDefinition () == typeof(ValueInfo<>)) { + if (valueType.IsConstructedGenericType && valueType.GetGenericTypeDefinition () == typeof (ValueInfo<>)) { source = (ValueSource)valueType.GetProperty ("Source").GetValue (value); value = valueType.GetProperty ("Value").GetValue (value); } - object newValue; - IPropertyConverter converter = property as IPropertyConverter; - if (converter != null && converter.TryConvert (value, typeof(T), out newValue)) { + if (property is IPropertyConverter converter && converter.TryConvert (value, typeof (T), out object newValue)) { return new ValueInfo<T> { Source = source, Value = (T)newValue diff --git a/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockPropertyInfo.cs b/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockPropertyInfo.cs index dc44742..47046ca 100644 --- a/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockPropertyInfo.cs +++ b/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockPropertyInfo.cs @@ -2,13 +2,13 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using Xamarin.PropertyEditing.Tests.MockControls; namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo { public class MockPropertyInfo<T> : IPropertyInfo, IPropertyConverter, IEquatable<MockPropertyInfo<T>> { - public MockPropertyInfo (string name, string category = "", bool canWrite = true, IEnumerable<Type> converterTypes = null) + public MockPropertyInfo (string name, string category = "", + bool canWrite = true, IEnumerable<Type> converterTypes = null) { Name = name; Category = category; @@ -17,7 +17,7 @@ namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo this.typeConverters = converterTypes .Where (type => type != null && typeof (TypeConverter).IsAssignableFrom (type)) .Select (type => (TypeConverter)Activator.CreateInstance (type)) - .ToArray(); + .ToArray (); } } @@ -35,7 +35,7 @@ namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo { toValue = null; if (this.typeConverters != null) { - foreach (var converter in this.typeConverters) { + foreach (TypeConverter converter in this.typeConverters) { if (converter.CanConvertTo (toType)) { toValue = converter.ConvertTo (fromValue, toType); return true; @@ -43,7 +43,7 @@ namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo } } - if (toType == typeof(string)) { + if (toType == typeof (string)) { toValue = fromValue?.ToString (); return true; } @@ -52,15 +52,15 @@ namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo toValue = Convert.ChangeType (fromValue, toType); return true; } catch { - + } - + return false; } public bool Equals (MockPropertyInfo<T> other) { - if (ReferenceEquals (null, other)) + if (other is null) return false; if (ReferenceEquals (this, other)) return true; @@ -72,11 +72,11 @@ namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo public override bool Equals (object obj) { - if (ReferenceEquals (null, obj)) + if (obj is null) return false; if (ReferenceEquals (this, obj)) return true; - if (GetType() != obj.GetType ()) + if (GetType () != obj.GetType ()) return false; return Equals ((MockPropertyInfo<T>)obj); @@ -95,6 +95,37 @@ namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo return hashCode; } - private readonly IReadOnlyList<TypeConverter> typeConverters; + readonly IReadOnlyList<TypeConverter> typeConverters; + } + + public class MockComplexPropertyInfo<T> : MockPropertyInfo<T>, IComplexPropertyInfo + { + public MockComplexPropertyInfo (string name, string category = "", + bool canWrite = true, IEnumerable<Type> converterTypes = null) : base (name, category, canWrite, converterTypes) + { + this.subProperties = new List<ISubPropertyInfo> (); + } + + public IReadOnlyCollection<ISubPropertyInfo> Properties => this.subProperties.ToArray (); + + public MockSubPropertyInfo<V> AddSubProperty<V> (string name, string category = "", + bool canWrite = true, IEnumerable<Type> converterTypes = null) + { + var subProperty = new MockSubPropertyInfo<V> (this, name, category, canWrite, converterTypes); + this.subProperties.Add (subProperty); + return subProperty; + } + + readonly List<ISubPropertyInfo> subProperties; + } + + public class MockSubPropertyInfo<T> : MockPropertyInfo<T>, ISubPropertyInfo + { + public MockSubPropertyInfo (IComplexPropertyInfo parentProperty, string name, string category = "", bool canWrite = true, IEnumerable<Type> converterTypes = null) : base (name, category, canWrite, converterTypes) + { + ParentProperty = parentProperty; + } + + public IComplexPropertyInfo ParentProperty { get; } } } diff --git a/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj b/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj index 7fcba52..13fbc09 100644 --- a/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj +++ b/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj @@ -61,6 +61,7 @@ <Compile Include="BrushPropertyViewModelTests.cs" /> <Compile Include="BytePropertyViewModelTests.cs" /> <Compile Include="CommonColorTests.cs" /> + <Compile Include="ComplexPropertyViewModelTests.cs" /> <Compile Include="Helpers.cs" /> <Compile Include="IPropertyConverter.cs" /> <Compile Include="MockControls\MockControl.cs" /> diff --git a/Xamarin.PropertyEditing.Windows/DoubleToPercentageConverter.cs b/Xamarin.PropertyEditing.Windows/DoubleToPercentageConverter.cs index 1d8074d..af3da92 100644 --- a/Xamarin.PropertyEditing.Windows/DoubleToPercentageConverter.cs +++ b/Xamarin.PropertyEditing.Windows/DoubleToPercentageConverter.cs @@ -11,11 +11,19 @@ namespace Xamarin.PropertyEditing.Windows { public object Convert (object value, Type targetType, object parameter, CultureInfo culture) { - if (!(value is double)) return DependencyProperty.UnsetValue; + if (!(value is double) && !(value is ValueInfo<double>)) return DependencyProperty.UnsetValue; - var doubleValue = (double)value; - if (doubleValue < 0 || doubleValue > 1) return DependencyProperty.UnsetValue; - return doubleValue * 100; + double doubleValue; + if (value is double) { + doubleValue = (double)value; + } else if (value is ValueInfo<double> doubleValueInfo && doubleValueInfo.Source == ValueSource.Local) { + doubleValue = doubleValueInfo.Value; + } else { + return DependencyProperty.UnsetValue; + } + + if (doubleValue< 0 || doubleValue> 1) return DependencyProperty.UnsetValue; + return doubleValue* 100; } public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) @@ -24,7 +32,15 @@ namespace Xamarin.PropertyEditing.Windows var doubleValue = (double)value; if ((doubleValue < 0) || (doubleValue > 100)) return DependencyProperty.UnsetValue; - return doubleValue / 100; + + var convertedValue = doubleValue / 100; + + if (targetType.IsAssignableFrom (typeof (double))) return convertedValue; + if (targetType.IsAssignableFrom (typeof (ValueInfo<double>))) return new ValueInfo<double> { + Source = ValueSource.Local, + Value = convertedValue + }; + return DependencyProperty.UnsetValue; } public override object ProvideValue (IServiceProvider serviceProvider) => this; diff --git a/Xamarin.PropertyEditing.Windows/SolidBrushEditorControl.cs b/Xamarin.PropertyEditing.Windows/SolidBrushEditorControl.cs index 5e3a1df..b7e1074 100644 --- a/Xamarin.PropertyEditing.Windows/SolidBrushEditorControl.cs +++ b/Xamarin.PropertyEditing.Windows/SolidBrushEditorControl.cs @@ -13,26 +13,22 @@ namespace Xamarin.PropertyEditing.Windows DefaultStyleKey = typeof (SolidBrushEditorControl); } - ComboBox colorSpacePicker; - - BrushPropertyViewModel ViewModel => DataContext as BrushPropertyViewModel; - public override void OnApplyTemplate () { base.OnApplyTemplate (); if (ViewModel == null) return; - colorSpacePicker = (ComboBox)GetTemplateChild ("colorSpacePicker"); + this.colorSpacePicker = (ComboBox)GetTemplateChild ("colorSpacePicker"); if (ViewModel.Solid.ColorSpaces == null || ViewModel.Solid.ColorSpaces.Count == 0) { - colorSpacePicker.Visibility = Visibility.Collapsed; + this.colorSpacePicker.Visibility = Visibility.Collapsed; } if (ViewModel.Property.CanWrite) { // Handle color space changes - colorSpacePicker.SelectionChanged += (s, e) => { + this.colorSpacePicker.SelectionChanged += (s, e) => { if (ViewModel != null && ViewModel.Value != null) { - ViewModel.Value = new CommonSolidBrush (ViewModel.Solid.Color, (string)e.AddedItems[0]); + ViewModel.Value = new CommonSolidBrush (ViewModel.Solid.Color.Value, (string)e.AddedItems[0]); } }; } @@ -45,5 +41,9 @@ namespace Xamarin.PropertyEditing.Windows ViewModel.Solid.CommitShade (); })); } + + ComboBox colorSpacePicker; + + BrushPropertyViewModel ViewModel => DataContext as BrushPropertyViewModel; } } diff --git a/Xamarin.PropertyEditing.Windows/StringValueInfoConverter.cs b/Xamarin.PropertyEditing.Windows/StringValueInfoConverter.cs new file mode 100644 index 0000000..24f2dcb --- /dev/null +++ b/Xamarin.PropertyEditing.Windows/StringValueInfoConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; + +namespace Xamarin.PropertyEditing.Windows +{ + [ValueConversion (typeof (ValueInfo<string>), typeof (string))] + internal class StringValueInfoConverter : MarkupExtension, IValueConverter + { + public object Convert (object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is string) && !(value is ValueInfo<string>)) return DependencyProperty.UnsetValue; + + if (value is ValueInfo<string> stringValueInfo) return stringValueInfo.Value; + return (string)value; + } + + public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is string stringValue)) return DependencyProperty.UnsetValue; + + return new ValueInfo<string> { + Source = ValueSource.Local, + Value = stringValue + }; + } + + public override object ProvideValue (IServiceProvider serviceProvider) => this; + } +} diff --git a/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml b/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml index c07f23e..cccc5ed 100644 --- a/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml +++ b/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml @@ -567,37 +567,56 @@ <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:SolidBrushEditorControl"> - <Grid VerticalAlignment="Top" Margin="0,6,0,0" MinHeight="180"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto"/> - <RowDefinition Height="*"/> - </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" MinWidth="200"/> - <ColumnDefinition Width="Auto"/> - </Grid.ColumnDefinitions> - <ComboBox Name="colorSpacePicker" Grid.ColumnSpan="2" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Solid.ColorSpaces}" - SelectedItem="{Binding Solid.ColorSpace, Mode=OneTime}" VerticalContentAlignment="Center" - AutomationProperties.Name="{x:Static prop:Resources.ColorSpace}" AutomationProperties.HelpText="{x:Static prop:Resources.ColorSpace}" ToolTip="{x:Static prop:Resources.ColorSpace}"/> - <Grid Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="*"/> - <RowDefinition Height="Auto"/> - </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*"/> - <ColumnDefinition Width="Auto"/> - </Grid.ColumnDefinitions> - <local:ShadeEditorControl x:Name="shadeChooser" Color="{Binding Path=Solid.Shade, Mode=TwoWay}" HueColor="{Binding Path=Solid.HueColor, Mode=OneWay}" - Grid.Column="0" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0,6,0,1" Panel.ZIndex="1"/> - <local:HueEditorControl x:Name="hueChooser" HueColor="{Binding Path=Solid.HueColor, Mode=TwoWay}" - Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="1,6,3,1" Panel.ZIndex="0" Focusable="False"/> - <local:CurrentColorEditorControl Color="{Binding Path=Solid.Shade, Mode=TwoWay}" InitialColor="{Binding Solid.InitialColor}" LastColor="{Binding Solid.LastColor}" - Grid.Column="0" Grid.Row="1" HorizontalAlignment="Stretch" Height="20" Margin="0,0,0,0" Panel.ZIndex="0" Focusable="False"/> - </Grid> - <local:ColorComponentsEditorControl x:Name="componentEditor" Color="{Binding Path=Solid.Shade, Mode=TwoWay}" Margin="0,4,0,0" - Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Focusable="False"/> - </Grid> + <TabControl Margin="0,4,0,0"> + <TabItem> + <TabItem.Header> + <local:PropertyPresenter Label="{x:Static prop:Resources.ColorEditorTabLabel}"/> + </TabItem.Header> + <Grid VerticalAlignment="Top" Margin="0,6,0,0" MinHeight="180"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="*"/> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" MinWidth="150"/> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> + <local:PropertyPresenter Label="{x:Static prop:Resources.ColorSpace}" Grid.ColumnSpan="2" Grid.Row="0" Grid.Column="0"> + <ComboBox + Name="colorSpacePicker" ItemsSource="{Binding Solid.ColorSpaces}" + SelectedItem="{Binding Solid.ColorSpace, Mode=OneTime, Converter={local:StringValueInfoConverter}}" VerticalContentAlignment="Center" + AutomationProperties.Name="{x:Static prop:Resources.ColorSpace}" AutomationProperties.HelpText="{x:Static prop:Resources.ColorSpace}" ToolTip="{x:Static prop:Resources.ColorSpace}"/> + </local:PropertyPresenter> + <Grid Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="*"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*"/> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> + <local:ShadeEditorControl + x:Name="shadeChooser" Color="{Binding Path=Solid.Shade, Mode=TwoWay}" HueColor="{Binding Path=Solid.HueColor, Mode=OneWay}" + Grid.Column="0" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0,6,0,1" Panel.ZIndex="1"/> + <local:HueEditorControl + x:Name="hueChooser" HueColor="{Binding Path=Solid.HueColor, Mode=TwoWay}" + Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="1,6,3,1" Panel.ZIndex="0" Focusable="False"/> + <local:CurrentColorEditorControl + Color="{Binding Path=Solid.Shade, Mode=TwoWay}" InitialColor="{Binding Solid.InitialColor}" LastColor="{Binding Solid.LastColor}" + Grid.Column="0" Grid.Row="1" HorizontalAlignment="Stretch" Height="20" Margin="0,0,0,0" Panel.ZIndex="0" Focusable="False"/> + </Grid> + <local:ColorComponentsEditorControl x:Name="componentEditor" + Color="{Binding Path=Solid.Shade, Mode=TwoWay}" Margin="0,4,0,0" + Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Focusable="False"/> + </Grid> + </TabItem> + <!--<TabItem> + <TabItem.Header> + <Label>Color Resources</Label> + </TabItem.Header> + </TabItem>--> + </TabControl> </ControlTemplate> </Setter.Value> </Setter> diff --git a/Xamarin.PropertyEditing.Windows/Xamarin.PropertyEditing.Windows.csproj b/Xamarin.PropertyEditing.Windows/Xamarin.PropertyEditing.Windows.csproj index 037d0d4..c2fb458 100644 --- a/Xamarin.PropertyEditing.Windows/Xamarin.PropertyEditing.Windows.csproj +++ b/Xamarin.PropertyEditing.Windows/Xamarin.PropertyEditing.Windows.csproj @@ -70,6 +70,7 @@ <Compile Include="DoubleToPercentageConverter.cs" /> <Compile Include="DoubleToQuantityConverter.cs" /> <Compile Include="TextBoxEx.cs" /> + <Compile Include="StringValueInfoConverter.cs" /> <Compile Include="ToggleButtonEx.cs" /> <Compile Include="EditorPropertySelector.cs" /> <Compile Include="EnumEditorControl.cs" /> diff --git a/Xamarin.PropertyEditing/BrushPropertyInfo.cs b/Xamarin.PropertyEditing/BrushPropertyInfo.cs index 7c2e3bc..a040efc 100644 --- a/Xamarin.PropertyEditing/BrushPropertyInfo.cs +++ b/Xamarin.PropertyEditing/BrushPropertyInfo.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using Xamarin.PropertyEditing.Drawing; +using Xamarin.PropertyEditing.ViewModels; namespace Xamarin.PropertyEditing { - public class BrushPropertyInfo : IPropertyInfo, IColorSpaced + public class BrushPropertyInfo : IComplexPropertyInfo, IColorSpaced { public BrushPropertyInfo (string name, string category, bool canWrite, IReadOnlyList<string> colorSpaces = null, ValueSources valueSources = ValueSources.Local, @@ -24,7 +25,7 @@ namespace Xamarin.PropertyEditing public string Name { get; } - public Type Type => typeof(CommonBrush); + public Type Type => typeof (CommonBrush); public string Category { get; } @@ -35,5 +36,104 @@ namespace Xamarin.PropertyEditing public IReadOnlyList<PropertyVariation> Variations { get; } public IReadOnlyList<IAvailabilityConstraint> AvailabilityConstraints { get; } + + public IReadOnlyCollection<ISubPropertyInfo> Properties => + this.properties ?? ( + this.properties = new ISubPropertyInfo[] { + OpacityInfo, + ColorSpaceInfo, + ColorInfo + }); + + public OpacityPropertyInfo OpacityInfo => + this.opacityInfo ?? ( + this.opacityInfo = new OpacityPropertyInfo (this)); + + public ColorSpacePropertyInfo ColorSpaceInfo => + this.colorSpaceInfo ?? ( + this.colorSpaceInfo = new ColorSpacePropertyInfo (this)); + + public ColorPropertyInfo ColorInfo => + this.colorInfo ?? ( + this.colorInfo = new ColorPropertyInfo (this)); + + OpacityPropertyInfo opacityInfo; + ColorSpacePropertyInfo colorSpaceInfo; + ColorPropertyInfo colorInfo; + IReadOnlyCollection<ISubPropertyInfo> properties; + + public class OpacityPropertyInfo : ISubPropertyInfo + { + public OpacityPropertyInfo (BrushPropertyInfo parent) + { + ParentProperty = parent; + } + + public IComplexPropertyInfo ParentProperty { get; } + + public string Name => nameof(BrushPropertyViewModel.Opacity); + + public Type Type => typeof (double); + + public string Category => null; + + public bool CanWrite => true; + + public ValueSources ValueSources => + ValueSources.Local | ValueSources.Default; + + public IReadOnlyList<PropertyVariation> Variations => null; + + public IReadOnlyList<IAvailabilityConstraint> AvailabilityConstraints => null; + } + + public class ColorSpacePropertyInfo : ISubPropertyInfo + { + public ColorSpacePropertyInfo (BrushPropertyInfo parent) + { + ParentProperty = parent; + } + + public IComplexPropertyInfo ParentProperty { get; } + + public string Name => nameof (SolidBrushViewModel.ColorSpace); + + public Type Type => typeof (string); + + public string Category => null; + + public bool CanWrite => true; + + public ValueSources ValueSources => + ValueSources.Local | ValueSources.Default; + + public IReadOnlyList<PropertyVariation> Variations => null; + + public IReadOnlyList<IAvailabilityConstraint> AvailabilityConstraints => null; + } + + public class ColorPropertyInfo : ISubPropertyInfo + { + public ColorPropertyInfo(BrushPropertyInfo parent) + { + ParentProperty = parent; + } + + public IComplexPropertyInfo ParentProperty { get; } + + public string Name => nameof(SolidBrushViewModel.Color); + + public Type Type => typeof(CommonColor); + + public string Category => null; + + public bool CanWrite => true; + + public ValueSources ValueSources => ValueSources.Local | ValueSources.Default; + + public IReadOnlyList<PropertyVariation> Variations => null; + + public IReadOnlyList<IAvailabilityConstraint> AvailabilityConstraints => null; + } } } diff --git a/Xamarin.PropertyEditing/IComplexPropertyInfo.cs b/Xamarin.PropertyEditing/IComplexPropertyInfo.cs new file mode 100644 index 0000000..3d1cb5f --- /dev/null +++ b/Xamarin.PropertyEditing/IComplexPropertyInfo.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Xamarin.PropertyEditing +{ + public interface IComplexPropertyInfo : IPropertyInfo + { + IReadOnlyCollection<ISubPropertyInfo> Properties { get; } + } +} diff --git a/Xamarin.PropertyEditing/ISubPropertyInfo.cs b/Xamarin.PropertyEditing/ISubPropertyInfo.cs new file mode 100644 index 0000000..7793d51 --- /dev/null +++ b/Xamarin.PropertyEditing/ISubPropertyInfo.cs @@ -0,0 +1,7 @@ +namespace Xamarin.PropertyEditing +{ + public interface ISubPropertyInfo : IPropertyInfo + { + IComplexPropertyInfo ParentProperty { get; } + } +} diff --git a/Xamarin.PropertyEditing/ViewModels/BrushPropertyViewModel.cs b/Xamarin.PropertyEditing/ViewModels/BrushPropertyViewModel.cs index 2eee16d..5139267 100644 --- a/Xamarin.PropertyEditing/ViewModels/BrushPropertyViewModel.cs +++ b/Xamarin.PropertyEditing/ViewModels/BrushPropertyViewModel.cs @@ -17,25 +17,32 @@ namespace Xamarin.PropertyEditing.ViewModels public SolidBrushViewModel Solid { get; } - public double Opacity { - get => Value == null ? 1.0 : Value.Opacity; + public ValueInfo<double> Opacity + { + get => this.opacity ?? ( + this.opacity = new ValueInfo<double> { + Source = ValueSource.Local, + Value = Value == null ? 1.0 : Value.Opacity + }); set { - if (Value is null) return; + this.opacity = value; + if (Value is null || value.Source != ValueSource.Local) return; + var opacity = value.Value; if (Value is CommonSolidBrush solid) { - Value = new CommonSolidBrush (solid.Color, solid.ColorSpace, value); + Value = new CommonSolidBrush (solid.Color, solid.ColorSpace, opacity); } else if (Value is CommonImageBrush img) { Value = new CommonImageBrush ( img.ImageSource, img.AlignmentX, img.AlignmentY, img.Stretch, img.TileMode, - img.ViewBox, img.ViewBoxUnits, img.ViewPort, img.ViewPortUnits, value); + img.ViewBox, img.ViewBoxUnits, img.ViewPort, img.ViewPortUnits, opacity); } else if (Value is CommonLinearGradientBrush linear) { Value = new CommonLinearGradientBrush ( linear.StartPoint, linear.EndPoint, linear.GradientStops, - linear.ColorInterpolationMode, linear.MappingMode, linear.SpreadMethod, value); + linear.ColorInterpolationMode, linear.MappingMode, linear.SpreadMethod, opacity); } else if (Value is CommonRadialGradientBrush radial) { Value = new CommonRadialGradientBrush ( radial.Center, radial.GradientOrigin, radial.RadiusX, radial.RadiusY, radial.GradientStops, radial.ColorInterpolationMode, radial.MappingMode, - radial.SpreadMethod, value); + radial.SpreadMethod, opacity); } else { throw new InvalidOperationException ("Value is an unsupported brush type."); } @@ -48,5 +55,7 @@ namespace Xamarin.PropertyEditing.ViewModels base.UpdateCurrentValue (); OnPropertyChanged (nameof (Opacity)); } + + ValueInfo<double> opacity; } } diff --git a/Xamarin.PropertyEditing/ViewModels/SolidBrushViewModel.cs b/Xamarin.PropertyEditing/ViewModels/SolidBrushViewModel.cs index b8bdb32..4f13b64 100644 --- a/Xamarin.PropertyEditing/ViewModels/SolidBrushViewModel.cs +++ b/Xamarin.PropertyEditing/ViewModels/SolidBrushViewModel.cs @@ -18,18 +18,32 @@ namespace Xamarin.PropertyEditing.ViewModels public CommonSolidBrush PreviousSolidBrush { get; set; } - public string ColorSpace => Parent.Value is CommonSolidBrush solidBrush ? solidBrush.ColorSpace : null; + public ValueInfo<string> ColorSpace + { + get => this.colorSpace ?? (this.colorSpace = new ValueInfo<string> { + Source = ValueSource.Local, + Value = Parent.Value is CommonSolidBrush solidBrush ? solidBrush.ColorSpace : null + }); + set { + this.colorSpace = value; + OnPropertyChanged (); + SetParentValue (Color.Value, value.Value, Parent.Value.Opacity); + } + } public CommonColor HueColor { get => (this.hueColor ?? (this.hueColor = LastColor.HueColor)).Value; set { if (!this.hueColor.Equals (value)) { - var saturation = Color.Saturation; - var brightness = Color.Brightness; - Color = CommonColor.FromHSB (value.Hue, saturation, brightness, Color.A); + var saturation = Color.Value.Saturation; + var brightness = Color.Value.Brightness; + Color = new ValueInfo<CommonColor> { + Source = ValueSource.Local, + Value = CommonColor.FromHSB (value.Hue, saturation, brightness, Color.Value.A) + }; this.hueColor = value; OnPropertyChanged (); - Parent.Value = new CommonSolidBrush (Color, ColorSpace, Parent.Value.Opacity); + SetParentValue (Color.Value, ColorSpace.Value, Parent.Value.Opacity); } } } @@ -40,48 +54,54 @@ namespace Xamarin.PropertyEditing.ViewModels if (!this.shade.Equals (value)) { this.shade = value; OnPropertyChanged (); - Parent.Value = new CommonSolidBrush (value, ColorSpace, Parent.Value.Opacity); + SetParentValue (value, ColorSpace.Value, Parent.Value.Opacity); } } } - public CommonColor Color { - get => Parent.Value is CommonSolidBrush solidBrush ? solidBrush.Color : new CommonColor (0, 0, 0); + public ValueInfo<CommonColor> Color { + get => this.color ?? ( + this.color = new ValueInfo<CommonColor> { + Source = ValueSource.Local, + Value = Parent.Value is CommonSolidBrush solidBrush ? solidBrush.Color : new CommonColor (0, 0, 0) + }); set { if (!Color.Equals (value)) { + this.color = value; CommonColor oldHue = HueColor; - CommonColor newHue = value.HueColor; - Parent.Value = new CommonSolidBrush (value, ColorSpace, Parent.Value.Opacity); + CommonColor newColor = value.Value; + CommonColor newHue = newColor.HueColor; + SetParentValue (newColor, ColorSpace.Value, Parent.Value.Opacity); OnPropertyChanged (); if (!newHue.Equals (oldHue)) { - hueColor = newHue; + this.hueColor = newHue; OnPropertyChanged (nameof (HueColor)); } - if (!value.Equals (shade)) { - shade = value; + if (!newColor.Equals (this.shade)) { + this.shade = newColor; OnPropertyChanged (nameof (Shade)); } if (!this.initialColor.HasValue) { - this.initialColor = value; + this.initialColor = newColor; } } } } - public CommonColor InitialColor => this.initialColor ?? (this.initialColor = Color).Value; + public CommonColor InitialColor => this.initialColor ?? (this.initialColor = Color.Value).Value; - public CommonColor LastColor => this.lastColor ?? (this.lastColor = Color).Value; + public CommonColor LastColor => this.lastColor ?? (this.lastColor = Color.Value).Value; public void CommitLastColor () { - this.lastColor = Color; - this.shade = Color; - this.hueColor = Color.HueColor; + this.lastColor = Color.Value; + this.shade = Color.Value; + this.hueColor = Color.Value.HueColor; var opacity = Parent.Value != null ? Parent.Value.Opacity : 1.0; OnPropertyChanged (nameof (LastColor)); OnPropertyChanged (nameof (Shade)); OnPropertyChanged (nameof (HueColor)); - Parent.Value = new CommonSolidBrush (Color, ColorSpace, opacity); + SetParentValue (Color.Value, ColorSpace.Value, opacity); } public void CommitShade () @@ -89,15 +109,28 @@ namespace Xamarin.PropertyEditing.ViewModels this.lastColor = Shade; var opacity = Parent.Value != null ? Parent.Value.Opacity : 1.0; OnPropertyChanged (nameof (LastColor)); - Parent.Value = new CommonSolidBrush (Shade, ColorSpace, opacity); + SetParentValue (Shade, ColorSpace.Value, opacity); } - private BrushPropertyViewModel Parent { get; } + public void SetParentValue (CommonColor color, string colorSpace, double opacity) + { + Parent.Value = new CommonSolidBrush (color, colorSpace, opacity); + // If the new value corresponds to a different color, we want to reset the Color value info, + // but not otherwise. + if (this.color != null && this.color.Value != color) { + this.color = null; + } + } + + BrushPropertyViewModel Parent { get; } + + CommonColor? hueColor; + CommonColor? shade; + CommonColor? initialColor; + CommonColor? lastColor; - private CommonColor? hueColor; - private CommonColor? shade; - private CommonColor? initialColor; - private CommonColor? lastColor; + ValueInfo<string> colorSpace; + ValueInfo<CommonColor> color; private void Parent_PropertyChanged (object sender, PropertyChangedEventArgs e) { diff --git a/Xamarin.PropertyEditing/Xamarin.PropertyEditing.csproj b/Xamarin.PropertyEditing/Xamarin.PropertyEditing.csproj index b2ee7d7..39c4796 100644 --- a/Xamarin.PropertyEditing/Xamarin.PropertyEditing.csproj +++ b/Xamarin.PropertyEditing/Xamarin.PropertyEditing.csproj @@ -66,6 +66,7 @@ <Compile Include="IAvailabilityConstraint.cs" /> <Compile Include="IClampedPropertyInfo.cs" /> <Compile Include="IColorSpaced.cs" /> + <Compile Include="IComplexPropertyInfo.cs" /> <Compile Include="IEditorProvider.cs" /> <Compile Include="IObjectEventEditor.cs" /> <Compile Include="IEventInfo.cs" /> @@ -75,6 +76,7 @@ <Compile Include="IPropertyInfo.cs" /> <Compile Include="IResourceProvider.cs" /> <Compile Include="ISelfConstrainedPropertyInfo.cs" /> + <Compile Include="ISubPropertyInfo.cs" /> <Compile Include="MultiAvailabilityConstraint.cs" /> <Compile Include="NotifyingObject.cs" /> <Compile Include="ObservableCollectionEx.cs" /> |