Browse Source

Non-building checkpoint in MAJOR refactor.

pull/4/head
Steven Kirk 12 years ago
parent
commit
bfadaac5d9
  1. 66
      Perspex.Base/Binding.cs
  2. 23
      Perspex.Base/Contract.cs
  3. 13
      Perspex.Base/IDescription.cs
  4. 13
      Perspex.Base/IObservableDescription.cs
  5. 85
      Perspex.Base/Perspex.Base.csproj
  6. 50
      Perspex.Base/PerspexList.cs
  7. 646
      Perspex.Base/PerspexObject.cs
  8. 333
      Perspex.Base/PerspexProperty.cs
  9. 47
      Perspex.Base/PerspexPropertyChangedEventArgs.cs
  10. 20
      Perspex.Base/Platform/IPlatformThreadingInterface.cs
  11. 405
      Perspex.Base/PriorityValue.cs
  12. 30
      Perspex.Base/Properties/AssemblyInfo.cs
  13. 102
      Perspex.Base/Threading/Dispatcher.cs
  14. 25
      Perspex.Base/Threading/DispatcherFrame.cs
  15. 159
      Perspex.Base/Threading/DispatcherOperation.cs
  16. 140
      Perspex.Base/Threading/DispatcherTimer.cs
  17. 10
      Perspex.Base/packages.config
  18. 7
      Perspex.Controls.UnitTests/ContentControlTests.cs
  19. 23
      Perspex.Controls.UnitTests/ControlTests.cs
  20. 125
      Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj
  21. 36
      Perspex.Controls.UnitTests/Properties/AssemblyInfo.cs
  22. 2
      Perspex.Controls.UnitTests/TemplatedControlTests.cs
  23. 6
      Perspex.Controls.UnitTests/TestRoot.cs
  24. 5
      Perspex.Controls.UnitTests/packages.config
  25. 62
      Perspex.Controls/Border.cs
  26. 49
      Perspex.Controls/Button.cs
  27. 12
      Perspex.Controls/CheckBox.cs
  28. 53
      Perspex.Controls/ColumnDefinition.cs
  29. 12
      Perspex.Controls/ColumnDefinitions.cs
  30. 24
      Perspex.Controls/ContentControl.cs
  31. 151
      Perspex.Controls/ContentPresenter.cs
  32. 270
      Perspex.Controls/Control.cs
  33. 37
      Perspex.Controls/ControlExtensions.cs
  34. 40
      Perspex.Controls/ControlTemplate.cs
  35. 22
      Perspex.Controls/Controls.cs
  36. 68
      Perspex.Controls/DataTemplate.cs
  37. 20
      Perspex.Controls/DataTemplates.cs
  38. 65
      Perspex.Controls/Decorator.cs
  39. 20
      Perspex.Controls/DefinitionBase.cs
  40. 805
      Perspex.Controls/Grid.cs
  41. 121
      Perspex.Controls/GridLength.cs
  42. 24
      Perspex.Controls/HeaderedContentControl.cs
  43. 25
      Perspex.Controls/HeaderedItemsControl.cs
  44. 14
      Perspex.Controls/IGlobalDataTemplates.cs
  45. 13
      Perspex.Controls/IHeadered.cs
  46. 115
      Perspex.Controls/Image.cs
  47. 137
      Perspex.Controls/ItemsControl.cs
  48. 20
      Perspex.Controls/ItemsPanelTemplate.cs
  49. 178
      Perspex.Controls/ItemsPresenter.cs
  50. 45
      Perspex.Controls/Panel.cs
  51. 138
      Perspex.Controls/Perspex.Controls.csproj
  52. 30
      Perspex.Controls/Properties/AssemblyInfo.cs
  53. 53
      Perspex.Controls/RowDefinition.cs
  54. 12
      Perspex.Controls/RowDefinitions.cs
  55. 20
      Perspex.Controls/SelectingItemsControl.cs
  56. 155
      Perspex.Controls/StackPanel.cs
  57. 62
      Perspex.Controls/TabControl.cs
  58. 26
      Perspex.Controls/TabItem.cs
  59. 88
      Perspex.Controls/TabStrip.cs
  60. 74
      Perspex.Controls/TemplatedControl.cs
  61. 66
      Perspex.Controls/TextBlock.cs
  62. 133
      Perspex.Controls/TextBox.cs
  63. 125
      Perspex.Controls/TextBoxView.cs
  64. 28
      Perspex.Controls/ToggleButton.cs
  65. 107
      Perspex.Controls/TreeDataTemplate.cs
  66. 78
      Perspex.Controls/TreeView.cs
  67. 56
      Perspex.Controls/TreeViewItem.cs
  68. 10
      Perspex.Controls/packages.config
  69. 4
      Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj
  70. 2
      Perspex.Direct2D1.RenderTests/packages.config
  71. 5
      Perspex.Direct2D1/Perspex.Direct2D1.csproj
  72. 2
      Perspex.Direct2D1/packages.config
  73. 47
      Perspex.Input/FocusManager.cs
  74. 21
      Perspex.Input/IFocusManager.cs
  75. 12
      Perspex.Input/IInputDevice.cs
  76. 36
      Perspex.Input/IInputElement.cs
  77. 21
      Perspex.Input/IInputManager.cs
  78. 33
      Perspex.Input/IKeyboardDevice.cs
  79. 13
      Perspex.Input/IMouseDevice.cs
  80. 20
      Perspex.Input/IPointerDevice.cs
  81. 121
      Perspex.Input/InputElement.cs
  82. 64
      Perspex.Input/InputManager.cs
  83. 214
      Perspex.Input/Key.cs
  84. 20
      Perspex.Input/KeyEventArgs.cs
  85. 75
      Perspex.Input/KeyboardDevice.cs
  86. 163
      Perspex.Input/MouseDevice.cs
  87. 108
      Perspex.Input/Perspex.Input.csproj
  88. 21
      Perspex.Input/PointerEventArgs.cs
  89. 30
      Perspex.Input/Properties/AssemblyInfo.cs
  90. 27
      Perspex.Input/Raw/RawInputEventArgs.cs
  91. 31
      Perspex.Input/Raw/RawKeyEventArgs.cs
  92. 42
      Perspex.Input/Raw/RawMouseEventArgs.cs
  93. 10
      Perspex.Input/packages.config
  94. 13
      Perspex.Interactivity/IInteractive.cs
  95. 124
      Perspex.Interactivity/Interactive.cs
  96. 90
      Perspex.Interactivity/Perspex.Interactivity.csproj
  97. 30
      Perspex.Interactivity/Properties/AssemblyInfo.cs
  98. 97
      Perspex.Interactivity/RoutedEvent.cs
  99. 36
      Perspex.Interactivity/RoutedEventArgs.cs
  100. 10
      Perspex.Interactivity/packages.config

66
Perspex.Base/Binding.cs

@ -0,0 +1,66 @@
// -----------------------------------------------------------------------
// <copyright file="Binding.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
public enum BindingMode
{
Default,
OneWay,
TwoWay,
OneTime,
OneWayToSource,
}
public struct Binding
{
public BindingMode Mode
{
get;
set;
}
public BindingPriority Priority
{
get;
set;
}
public PerspexProperty Property
{
get;
set;
}
public PerspexObject Source
{
get;
set;
}
public static Binding operator !(Binding binding)
{
return binding.WithMode(BindingMode.TwoWay);
}
public static Binding operator ~(Binding binding)
{
return binding.WithMode(BindingMode.TwoWay);
}
public Binding WithMode(BindingMode mode)
{
this.Mode = mode;
return this;
}
public Binding WithPriority(BindingPriority priority)
{
this.Priority = priority;
return this;
}
}
}

23
Perspex.Base/Contract.cs

@ -0,0 +1,23 @@
// -----------------------------------------------------------------------
// <copyright file="Contract.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
using System;
public static class Contract
{
public static void Requires<TException>(bool condition) where TException : Exception, new()
{
#if DEBUG
if (!condition)
{
throw new TException();
}
#endif
}
}
}

13
Perspex.Base/IDescription.cs

@ -0,0 +1,13 @@
// -----------------------------------------------------------------------
// <copyright file="IDescription.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
public interface IDescription
{
string Description { get; }
}
}

13
Perspex.Base/IObservableDescription.cs

@ -0,0 +1,13 @@
// -----------------------------------------------------------------------
// <copyright file="IObservableDescription.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
public interface IObservableDescription
{
string Description { get; }
}
}

85
Perspex.Base/Perspex.Base.csproj

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex</RootNamespace>
<AssemblyName>Perspex.Base</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="Binding.cs" />
<Compile Include="Contract.cs" />
<Compile Include="IDescription.cs" />
<Compile Include="IObservableDescription.cs" />
<Compile Include="PerspexList.cs" />
<Compile Include="PerspexObject.cs" />
<Compile Include="PerspexProperty.cs" />
<Compile Include="PerspexPropertyChangedEventArgs.cs" />
<Compile Include="Platform\IPlatformThreadingInterface.cs" />
<Compile Include="PriorityValue.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Threading\Dispatcher.cs" />
<Compile Include="Threading\DispatcherFrame.cs" />
<Compile Include="Threading\DispatcherOperation.cs" />
<Compile Include="Threading\DispatcherTimer.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Splat">
<HintPath>..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Core">
<HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Interfaces">
<HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Linq">
<HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets" Condition="Exists('..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

50
Perspex.Base/PerspexList.cs

@ -0,0 +1,50 @@
// -----------------------------------------------------------------------
// <copyright file="PerspexList.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reactive.Linq;
public class PerspexList<T> : ObservableCollection<T>
{
public PerspexList()
{
this.Initialize();
}
public PerspexList(IEnumerable<T> items)
: base(items)
{
this.Initialize();
}
public IObservable<NotifyCollectionChangedEventArgs> Changed
{
get;
private set;
}
public void AddRange(IEnumerable<T> items)
{
foreach (T item in items)
{
this.Add(item);
}
}
private void Initialize()
{
this.Changed = Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
handler => (sender, e) => handler(e),
handler => this.CollectionChanged += handler,
handler => this.CollectionChanged -= handler);
}
}
}

646
Perspex.Base/PerspexObject.cs

@ -0,0 +1,646 @@
// -----------------------------------------------------------------------
// <copyright file="PerspexObject.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Splat;
/// <summary>
/// The priority of a binding.
/// </summary>
public enum BindingPriority
{
/// <summary>
/// A local value.
/// </summary>
LocalValue,
/// <summary>
/// A triggered style binding.
/// </summary>
/// <remarks>
/// A style trigger is a selector such as .class which overrides a
/// <see cref="TemplatedParent"/> binding. In this way, a basic control can have
/// for example a Background from the templated parent which changes when the
/// control has the :pointerover class.
/// </remarks>
StyleTrigger,
/// <summary>
/// A binding to a property on the templated parent.
/// </summary>
TemplatedParent,
/// <summary>
/// A style binding.
/// </summary>
Style,
/// <summary>
/// The binding is uninitialized.
/// </summary>
Unset = int.MaxValue,
}
/// <summary>
/// An object with <see cref="PerspexProperty"/> support.
/// </summary>
/// <remarks>
/// This class is analogous to DependencyObject in WPF.
/// </remarks>
public class PerspexObject : IEnableLogger
{
/// <summary>
/// The registered properties by type.
/// </summary>
private static Dictionary<Type, List<PerspexProperty>> registered =
new Dictionary<Type, List<PerspexProperty>>();
/// <summary>
/// The parent object that inherited values are inherited from.
/// </summary>
private PerspexObject inheritanceParent;
/// <summary>
/// The set values/bindings on this object.
/// </summary>
private Dictionary<PerspexProperty, PriorityValue> values =
new Dictionary<PerspexProperty, PriorityValue>();
/// <summary>
/// Raised when a <see cref="PerspexProperty"/> value changes on this object/
/// </summary>
public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
/// <summary>
/// Gets or sets the parent object that inherited <see cref="PerspexProperty"/> values
/// are inherited from.
/// </summary>
protected PerspexObject InheritanceParent
{
get
{
return this.inheritanceParent;
}
set
{
if (this.inheritanceParent != value)
{
if (this.inheritanceParent != null)
{
this.inheritanceParent.PropertyChanged -= this.ParentPropertyChanged;
}
var inherited = (from property in GetProperties(this.GetType())
where property.Inherits
select new
{
Property = property,
Value = this.GetValue(property),
}).ToList();
this.inheritanceParent = value;
foreach (var i in inherited)
{
object newValue = this.GetValue(i.Property);
if (!object.Equals(i.Value, newValue))
{
this.RaisePropertyChanged(i.Property, i.Value, newValue);
}
}
if (this.inheritanceParent != null)
{
this.inheritanceParent.PropertyChanged += this.ParentPropertyChanged;
}
}
}
}
/// <summary>
/// Gets or sets the value of a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="property">The property.</param>
public object this[PerspexProperty property]
{
get { return this.GetValue(property); }
set { this.SetValue(property, value); }
}
/// <summary>
/// Gets or sets a binding for a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="binding">The binding information.</param>
public Binding this[Binding binding]
{
get
{
return new Binding
{
Mode = binding.Mode,
Priority = binding.Priority,
Property = binding.Property,
Source = this,
};
}
set
{
BindingMode mode = (binding.Mode == BindingMode.Default) ?
binding.Property.DefaultBindingMode :
binding.Mode;
switch (mode)
{
case BindingMode.Default:
case BindingMode.OneWay:
this.Bind(binding.Property, value.Source.GetObservable(value.Property), binding.Priority);
break;
case BindingMode.OneTime:
this.SetValue(binding.Property, value.Source.GetValue(value.Property));
break;
case BindingMode.OneWayToSource:
value.Source.Bind(value.Property, this.GetObservable(binding.Property), binding.Priority);
break;
case BindingMode.TwoWay:
this.BindTwoWay(binding.Property, value.Source, value.Property);
break;
}
}
}
/// <summary>
/// Gets all <see cref="PerspexProperty"/>s registered on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
public static IEnumerable<PerspexProperty> GetProperties(Type type)
{
Contract.Requires<NullReferenceException>(type != null);
TypeInfo i = type.GetTypeInfo();
while (type != null)
{
List<PerspexProperty> list;
if (registered.TryGetValue(type, out list))
{
foreach (PerspexProperty p in list)
{
yield return p;
}
}
type = type.GetTypeInfo().BaseType;
}
}
/// <summary>
/// Registers a <see cref="PerspexProperty"/> on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="property">The property.</param>
/// <remarks>
/// You won't usually want to call this method directly, instead use the
/// <see cref="PerspexProperty.Register"/> method.
/// </remarks>
public static void Register(Type type, PerspexProperty property)
{
Contract.Requires<NullReferenceException>(type != null);
Contract.Requires<NullReferenceException>(property != null);
List<PerspexProperty> list;
if (!registered.TryGetValue(type, out list))
{
list = new List<PerspexProperty>();
registered.Add(type, list);
}
if (!list.Contains(property))
{
list.Add(property);
}
}
/// <summary>
/// Clears a <see cref="PerspexProperty"/>'s local value.
/// </summary>
/// <param name="property">The property.</param>
public void ClearValue(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
this.SetValue(property, PerspexProperty.UnsetValue);
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
public IObservable<object> GetObservable(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
return Observable.Create<object>(observer =>
{
EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
{
if (e.Property == property)
{
observer.OnNext(e.NewValue);
}
};
this.PropertyChanged += handler;
observer.OnNext(this.GetValue(property));
return () =>
{
this.PropertyChanged -= handler;
};
});
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="property"></param>
/// <returns></returns>
public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
{
Contract.Requires<NullReferenceException>(property != null);
return this.GetObservable((PerspexProperty)property).Cast<T>();
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="property"></param>
/// <returns></returns>
public IObservable<Tuple<T, T>> GetObservableWithHistory<T>(PerspexProperty<T> property)
{
return Observable.Create<Tuple<T, T>>(observer =>
{
EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
{
if (e.Property == property)
{
observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
}
};
this.PropertyChanged += handler;
return () =>
{
this.PropertyChanged -= handler;
};
});
}
/// <summary>
/// Gets a <see cref="PerspexProperty"/> value.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
public object GetValue(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
object result;
PriorityValue value;
if (this.values.TryGetValue(property, out value))
{
result = value.Value;
}
else
{
result = PerspexProperty.UnsetValue;
}
if (result == PerspexProperty.UnsetValue)
{
result = this.GetDefaultValue(property);
}
return result;
}
/// <summary>
/// Gets a <see cref="PerspexProperty"/> value.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
public T GetValue<T>(PerspexProperty<T> property)
{
Contract.Requires<NullReferenceException>(property != null);
return (T)this.GetValue((PerspexProperty)property);
}
/// <summary>
/// Gets all of the <see cref="PerspexProperty"/> values explicitly set on this object.
/// </summary>
public IEnumerable<Tuple<PerspexProperty, object, BindingPriority>> GetSetValues()
{
foreach (var value in this.values)
{
yield return Tuple.Create(
value.Key,
value.Value.Value,
(BindingPriority)value.Value.ValuePriority);
}
}
/// <summary>
/// Checks whether a <see cref="PerspexProperty"/> is set on this object.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>True if the property is set, otherwise false.</returns>
public bool IsSet(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
return this.values.ContainsKey(property);
}
/// <summary>
/// Checks whether a <see cref="PerspexProperty"/> is registered on this class.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>True if the property is registered, otherwise false.</returns>
private bool IsRegistered(PerspexProperty property)
{
Type type = this.GetType();
while (type != null)
{
List<PerspexProperty> list;
if (registered.TryGetValue(type, out list))
{
if (list.Contains(property))
{
return true;
}
}
type = type.GetTypeInfo().BaseType;
}
return false;
}
/// <summary>
/// Sets a <see cref="PerspexProperty"/> value.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
public void SetValue(PerspexProperty property, object value)
{
Contract.Requires<NullReferenceException>(property != null);
const int Priority = (int)BindingPriority.LocalValue;
PriorityValue v;
if (!this.IsRegistered(property))
{
throw new InvalidOperationException(string.Format(
"Property '{0}' not registered on '{1}'",
property.Name,
this.GetType()));
}
if (!PriorityValue.IsValidValue(value, property.PropertyType))
{
throw new InvalidOperationException(string.Format(
"Invalid value for Property '{0}': {1} ({2})",
property.Name,
value,
value.GetType().FullName));
}
if (!this.values.TryGetValue(property, out v))
{
if (value == PerspexProperty.UnsetValue)
{
return;
}
v = this.CreatePriorityValue(property);
this.values.Add(property, v);
}
this.Log().Debug(string.Format(
"Set local value of {0}.{1} (#{2:x8}) to {3}",
this.GetType().Name,
property.Name,
this.GetHashCode(),
value));
v.Replace(Observable.Never<object>().StartWith(value), Priority);
}
/// <summary>
/// Sets a <see cref="PerspexProperty"/> value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
public void SetValue<T>(PerspexProperty<T> property, T value)
{
Contract.Requires<NullReferenceException>(property != null);
this.SetValue((PerspexProperty)property, value);
}
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind(
PerspexProperty property,
IObservable<object> source,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<NullReferenceException>(property != null);
PriorityValue v;
IDescription description = source as IDescription;
if (!this.values.TryGetValue(property, out v))
{
v = this.CreatePriorityValue(property);
this.values.Add(property, v);
}
this.Log().Debug(string.Format(
"Bound value of {0}.{1} (#{2:x8}) to {3}",
this.GetType().Name,
property.Name,
this.GetHashCode(),
description != null ? description.Description : "[Anonymous]"));
if (priority == BindingPriority.LocalValue)
{
return v.Replace(source, (int)priority);
}
else
{
return v.Add(source, (int)priority);
}
}
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
PerspexProperty<T> property,
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<NullReferenceException>(property != null);
return this.Bind((PerspexProperty)property, (IObservable<object>)source, priority);
}
/// <summary>
/// Initialites a two-way bind between <see cref="PerspexProperty"/>s.
/// </summary>
/// <param name="property">The property on this object.</param>
/// <param name="source">The source object.</param>
/// <param name="sourceProperty">The property on the source object.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
/// <remarks>
/// The binding is first carried out from <paramref name="source"/> to this. Two-way
/// bindings are always at the LocalValue priority.
/// </remarks>
public void BindTwoWay(
PerspexProperty property,
PerspexObject source,
PerspexProperty sourceProperty)
{
source.GetObservable(sourceProperty).Subscribe(x => this.SetValue(property, x));
this.GetObservable(property).Subscribe(x => source.SetValue(sourceProperty, x));
}
private PriorityValue CreatePriorityValue(PerspexProperty property)
{
PriorityValue result = new PriorityValue(property.Name, property.PropertyType);
result.Changed.Subscribe(x =>
{
object oldValue = (x.Item1 == PerspexProperty.UnsetValue) ?
this.GetDefaultValue(property) :
x.Item1;
object newValue = (x.Item2 == PerspexProperty.UnsetValue) ?
this.GetDefaultValue(property) :
x.Item2;
if (!object.Equals(oldValue, newValue))
{
this.RaisePropertyChanged(property, oldValue, newValue);
this.Log().Debug(string.Format(
"Value of {0}.{1} (#{2:x8}) changed from {3} to {4}",
this.GetType().Name,
property.Name,
this.GetHashCode(),
oldValue,
newValue));
}
});
return result;
}
/// <summary>
/// Gets the default value for a property.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The default value.</returns>
private object GetDefaultValue(PerspexProperty property)
{
if (property.Inherits && this.inheritanceParent != null)
{
return this.inheritanceParent.GetValue(property);
}
else
{
return property.GetDefaultValue(this.GetType());
}
}
/// <summary>
/// Called when a property is changed on the current <see cref="InheritanceParent"/>.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
/// <remarks>
/// Checks for changes in an inherited property value.
/// </remarks>
private void ParentPropertyChanged(object sender, PerspexPropertyChangedEventArgs e)
{
Contract.Requires<ArgumentNullException>(e != null);
if (e.Property.Inherits && !this.IsSet(e.Property))
{
this.RaisePropertyChanged(e.Property, e.OldValue, e.NewValue);
}
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="property">The property that has changed.</param>
/// <param name="oldValue">The old property value.</param>
/// <param name="newValue">The new property value.</param>
private void RaisePropertyChanged(PerspexProperty property, object oldValue, object newValue)
{
Contract.Requires<NullReferenceException>(property != null);
if (this.PropertyChanged != null)
{
PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs(this, property, oldValue, newValue);
property.NotifyChanged(e);
this.PropertyChanged(this, e);
}
}
}
}

333
Perspex.Base/PerspexProperty.cs

@ -0,0 +1,333 @@
// -----------------------------------------------------------------------
// <copyright file="PerspexProperty.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
using System.Reflection;
/// <summary>
/// A perspex property.
/// </summary>
/// <remarks>
/// This class is analogous to DependencyProperty in WPF.
/// </remarks>
public class PerspexProperty
{
/// <summary>
/// Represents an unset property value.
/// </summary>
public static readonly object UnsetValue = new Unset();
/// <summary>
/// The default values for the property, by type.
/// </summary>
private Dictionary<Type, object> defaultValues = new Dictionary<Type, object>();
/// <summary>
/// Observable fired when this property changes on any <see cref="PerspexObject"/>.
/// </summary>
private Subject<PerspexPropertyChangedEventArgs> changed = new Subject<PerspexPropertyChangedEventArgs>();
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="valueType">The type of the property's value.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
public PerspexProperty(
string name,
Type valueType,
Type ownerType,
object defaultValue,
bool inherits,
BindingMode defaultBindingMode)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(valueType != null);
Contract.Requires<NullReferenceException>(ownerType != null);
this.Name = name;
this.PropertyType = valueType;
this.OwnerType = ownerType;
this.Inherits = inherits;
this.DefaultBindingMode = defaultBindingMode;
this.defaultValues.Add(ownerType, defaultValue);
}
/// <summary>
/// Gets the name of the property.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the type of the property's value.
/// </summary>
public Type PropertyType { get; private set; }
/// <summary>
/// Gets the type of the class that registers the property.
/// </summary>
public Type OwnerType { get; private set; }
/// <summary>
/// Gets a value indicating whether the property inherits its value.
/// </summary>
public bool Inherits { get; private set; }
/// <summary>
/// Gets the default binding mode for the property.
/// </summary>
/// <returns></returns>
public BindingMode DefaultBindingMode { get; private set; }
/// <summary>
/// Gets an observable that is fired when this property changes on any
/// <see cref="PerspexObject"/> instance.
/// </summary>
public IObservable<PerspexPropertyChangedEventArgs> Changed
{
get { return this.changed; }
}
/// <summary>
/// Registers a <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
/// <param name="name">The name of the property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
public static PerspexProperty<TValue> Register<TOwner, TValue>(
string name,
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay)
where TOwner : PerspexObject
{
Contract.Requires<NullReferenceException>(name != null);
PerspexProperty<TValue> result = new PerspexProperty<TValue>(
name,
typeof(TOwner),
defaultValue,
inherits,
defaultBindingMode);
PerspexObject.Register(typeof(TOwner), result);
return result;
}
/// <summary>
/// Registers an attached <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam>
/// <typeparam name="THost">The type of the class that the property is to be registered on.</typeparam>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
/// <param name="name">The name of the property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
public static PerspexProperty<TValue> RegisterAttached<TOwner, THost, TValue>(
string name,
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay)
where TOwner : PerspexObject
{
Contract.Requires<NullReferenceException>(name != null);
PerspexProperty<TValue> result = new PerspexProperty<TValue>(
name,
typeof(TOwner),
defaultValue,
inherits,
defaultBindingMode);
PerspexObject.Register(typeof(THost), result);
return result;
}
/// <summary>
/// Provides access to a property's binding via the <see cref="PerspexObject"/>
/// indexer.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>A <see cref="Binding"/> describing the binding.</returns>
public static Binding operator!(PerspexProperty property)
{
return new Binding
{
Priority = BindingPriority.LocalValue,
Property = property,
};
}
/// <summary>
/// Provides access to a property's template binding via the <see cref="PerspexObject"/>
/// indexer.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>A <see cref="Binding"/> describing the binding.</returns>
public static Binding operator ~(PerspexProperty property)
{
return new Binding
{
Priority = BindingPriority.TemplatedParent,
Property = property,
};
}
/// <summary>
/// Returns a binding accessor that can be passed to <see cref="PerspexObject"/>'s []
/// operator to initiate a binding.
/// </summary>
/// <returns>A <see cref="Binding"/>.</returns>
/// <remarks>
/// The ! and ~ operators are short forms of this.
/// </remarks>
public Binding Bind()
{
return new Binding
{
Property = this,
};
}
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The default value.</returns>
public object GetDefaultValue(Type type)
{
Contract.Requires<NullReferenceException>(type != null);
while (type != null)
{
object result;
if (this.defaultValues.TryGetValue(type, out result))
{
return result;
}
type = type.GetTypeInfo().BaseType;
}
return this.defaultValues[this.OwnerType];
}
public bool IsValidValue(object value)
{
if (value == UnsetValue)
{
return true;
}
else if (value == null)
{
return !this.PropertyType.GetTypeInfo().IsValueType ||
Nullable.GetUnderlyingType(this.PropertyType) != null;
}
return this.PropertyType.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo());
}
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue(Type type, object defaultValue)
{
Contract.Requires<NullReferenceException>(type != null);
// TODO: Ensure correct type.
if (this.defaultValues.ContainsKey(type))
{
throw new InvalidOperationException("Default value is already set for this property.");
}
this.defaultValues.Add(type, defaultValue);
}
public override string ToString()
{
return this.Name;
}
internal void NotifyChanged(PerspexPropertyChangedEventArgs e)
{
this.changed.OnNext(e);
}
private class Unset
{
public override string ToString()
{
return "{Unset}";
}
}
}
/// <summary>
/// A typed perspex property.
/// </summary>
public class PerspexProperty<TValue> : PerspexProperty
{
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
public PerspexProperty(
string name,
Type ownerType,
TValue defaultValue,
bool inherits,
BindingMode defaultBindingMode)
: base(name, typeof(TValue), ownerType, defaultValue, inherits, defaultBindingMode)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(ownerType != null);
}
/// <summary>
/// Registers the property on another type.
/// </summary>
/// <typeparam name="TOwner">The type of the additional owner.</typeparam>
/// <returns>The property.</returns>
public PerspexProperty<TValue> AddOwner<TOwner>()
{
PerspexObject.Register(typeof(TOwner), this);
return this;
}
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <returns>The default value.</returns>
public TValue GetDefaultValue<T>()
{
return (TValue)this.GetDefaultValue(typeof(T));
}
}
}

47
Perspex.Base/PerspexPropertyChangedEventArgs.cs

@ -0,0 +1,47 @@
// -----------------------------------------------------------------------
// <copyright file="PerspexPropertyChangedEventArgs.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
/// <summary>
/// Provides information for a perspex property change.
/// </summary>
public class PerspexPropertyChangedEventArgs
{
public PerspexPropertyChangedEventArgs(
PerspexObject sender,
PerspexProperty property,
object oldValue,
object newValue)
{
this.Sender = sender;
this.Property = property;
this.OldValue = oldValue;
this.NewValue = newValue;
}
/// <summary>
/// Gets the <see cref="PerspexObject"/> that the property changed on.
/// </summary>
/// <returns></returns>
public PerspexObject Sender { get; private set; }
/// <summary>
/// Gets the property that changed.
/// </summary>
public PerspexProperty Property { get; private set; }
/// <summary>
/// Gets the old value of the property.
/// </summary>
public object OldValue { get; private set; }
/// <summary>
/// Gets the new value of the property.
/// </summary>
public object NewValue { get; private set; }
}
}

20
Perspex.Base/Platform/IPlatformThreadingInterface.cs

@ -0,0 +1,20 @@
// -----------------------------------------------------------------------
// <copyright file="IPlatformThreadingInterface.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Platform
{
using System;
using Perspex.Threading;
public interface IPlatformThreadingInterface
{
Dispatcher GetThreadDispatcher();
void KillTimer(object timerHandle);
object StartTimer(TimeSpan interval, Action internalTick);
}
}

405
Perspex.Base/PriorityValue.cs

@ -0,0 +1,405 @@
// -----------------------------------------------------------------------
// <copyright file="PriorityValue.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Reflection;
/// <summary>
/// Maintains a list of prioritised bindings together with a current value.
/// </summary>
/// <remarks>
/// Bindings, in the form of <see cref="IObservable<object>"/>s are added to the object using
/// the <see cref="Add"/> method. With the observable is passed a priority, where lower values
/// represent higher priorites. The current <see cref="Value"/> is selected from the highest
/// priority binding that doesn't return <see cref="PerspexProperty.UnsetValue"/>. Where there
/// are multiple bindings registered with the same priority, the most recently added binding
/// has a higher priority. Each time the value changes to a distinct new value, the
/// <see cref="Changed"/> observable is fired with the old and new values.
/// </remarks>
public class PriorityValue
{
/// <summary>
/// The name of the property.
/// </summary>
private string name;
/// <summary>
/// The value type.
/// </summary>
private Type valueType;
/// <summary>
/// The currently registered binding entries.
/// </summary>
private LinkedList<BindingEntry> bindings = new LinkedList<BindingEntry>();
/// <summary>
/// The changed observable.
/// </summary>
private Subject<Tuple<object, object>> changed = new Subject<Tuple<object, object>>();
/// <summary>
/// The current value.
/// </summary>
private object value;
/// <summary>
/// Initializes a new instance of the <see cref="PriorityValue"/> class.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="valueType">The value type.</param>
public PriorityValue(string name, Type valueType)
{
this.name = name;
this.valueType = valueType;
this.value = PerspexProperty.UnsetValue;
this.ValuePriority = int.MaxValue;
}
/// <summary>
/// Fired whenever the current <see cref="Value"/> changes to a new distinct value.
/// </summary>
public IObservable<Tuple<object, object>> Changed
{
get { return this.changed; }
}
/// <summary>
/// Gets the current value.
/// </summary>
public object Value
{
get { return this.value; }
}
/// <summary>
/// Gets the priority of the binding that is currently active.
/// </summary>
public int ValuePriority
{
get;
private set;
}
/// <summary>
/// Checks whether a value is valid for a type.
/// </summary>
/// <param name="value"></param>
/// <param name="propertyType"></param>
/// <returns></returns>
public static bool IsValidValue(object value, Type propertyType)
{
TypeInfo type = propertyType.GetTypeInfo();
if (value == PerspexProperty.UnsetValue)
{
return true;
}
else if (value == null)
{
if (type.IsValueType &&
(!type.IsGenericType || !(type.GetGenericTypeDefinition() == typeof(Nullable<>))))
{
return false;
}
}
else
{
if (!type.IsAssignableFrom(value.GetType().GetTypeInfo()))
{
return false;
}
}
return true;
}
/// <summary>
/// Adds a new binding.
/// </summary>
/// <param name="binding">The binding.</param>
/// <param name="priority">The binding priority.</param>
/// <returns>
/// A disposable that will remove the binding.
/// </returns>
public IDisposable Add(IObservable<object> binding, int priority)
{
BindingEntry entry = new BindingEntry();
LinkedListNode<BindingEntry> insert = this.bindings.First;
while (insert != null && insert.Value.Priority < priority)
{
insert = insert.Next;
}
if (insert == null)
{
this.bindings.AddLast(entry);
}
else
{
this.bindings.AddBefore(insert, entry);
}
entry.Start(binding, priority, this.EntryChanged, this.EntryCompleted);
return Disposable.Create(() =>
{
this.Remove(entry);
});
}
/// <summary>
/// Adds a new binding, replacing all those of the same priority.
/// </summary>
/// <param name="binding">The binding.</param>
/// <param name="priority">The binding priority.</param>
/// <returns>
/// A disposable that will remove the binding.
/// </returns>
public IDisposable Replace(IObservable<object> binding, int priority)
{
BindingEntry entry = new BindingEntry();
LinkedListNode<BindingEntry> insert = this.bindings.First;
while (insert != null && insert.Value.Priority < priority)
{
insert = insert.Next;
}
while (insert != null && insert.Value.Priority == priority)
{
LinkedListNode<BindingEntry> next = insert.Next;
insert.Value.Dispose();
this.bindings.Remove(insert);
insert = next;
}
if (insert == null)
{
this.bindings.AddLast(entry);
}
else
{
this.bindings.AddBefore(insert, entry);
}
entry.Start(binding, priority, this.EntryChanged, this.EntryCompleted);
return Disposable.Create(() =>
{
this.Remove(entry);
});
}
/// <summary>
/// Removes all bindings with the specified priority.
/// </summary>
/// <param name="priority">The priority.</param>
public void Clear(int priority)
{
LinkedListNode<BindingEntry> item = this.bindings.First;
bool removed = false;
while (item != null && item.Value.Priority <= priority)
{
LinkedListNode<BindingEntry> next = item.Next;
if (item.Value.Priority == priority)
{
item.Value.Dispose();
this.bindings.Remove(item);
removed = true;
}
item = next;
}
if (removed && priority <= this.ValuePriority)
{
this.UpdateValue();
}
}
/// <summary>
/// Gets the currently active bindings on this object.
/// </summary>
/// <returns>An enumerable collection of bindings.</returns>
public IEnumerable<BindingEntry> GetBindings()
{
return this.bindings;
}
/// <summary>
/// Called when a binding's value changes.
/// </summary>
/// <param name="changed">The changed entry.</param>
private void EntryChanged(BindingEntry changed)
{
if (changed.Priority <= this.ValuePriority)
{
this.UpdateValue();
}
}
/// <summary>
/// Called when a binding completes.
/// </summary>
/// <param name="changed">The completed entry.</param>
private void EntryCompleted(BindingEntry entry)
{
this.Remove(entry);
}
/// <summary>
/// Sets the current value and notifies all observers.
/// </summary>
/// <param name="value">The new value.</param>
/// <param name="priority">The priority of the binding which produced the value.</param>
private void SetValue(object value, int priority)
{
if (!IsValidValue(value, this.valueType))
{
throw new InvalidOperationException(string.Format(
"Invalid value for Property '{0}': {1} ({2})",
this.name,
value,
value.GetType().FullName));
}
object old = this.value;
this.ValuePriority = priority;
if (!EqualityComparer<object>.Default.Equals(old, value))
{
this.value = value;
this.changed.OnNext(Tuple.Create(old, value));
}
}
/// <summary>
/// Removes the specified binding entry and updates the current value.
/// </summary>
/// <param name="entry">The binding entry to remove.</param>
private void Remove(BindingEntry entry)
{
entry.Dispose();
this.bindings.Remove(entry);
this.UpdateValue();
}
/// <summary>
/// Updates the current value.
/// </summary>
private void UpdateValue()
{
foreach (BindingEntry entry in this.bindings)
{
if (entry.Value != PerspexProperty.UnsetValue)
{
this.SetValue(entry.Value, entry.Priority);
return;
}
}
this.SetValue(PerspexProperty.UnsetValue, int.MaxValue);
}
/// <summary>
/// A registered binding.
/// </summary>
public class BindingEntry : IDisposable
{
/// <summary>
/// The binding subscription.
/// </summary>
private IDisposable subscription;
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description
{
get;
private set;
}
/// <summary>
/// The priority of the binding.
/// </summary>
public int Priority
{
get;
private set;
}
/// <summary>
/// The current value of the binding.
/// </summary>
public object Value
{
get;
private set;
}
/// <summary>
/// Starts listening to the specified binding.
/// </summary>
/// <param name="binding">The binding.</param>
/// <param name="priority">The binding priority.</param>
/// <param name="changed">Called when the binding changes.</param>
/// <param name="completed">Called when the binding completes.</param>
public void Start(
IObservable<object> binding,
int priority,
Action<BindingEntry> changed,
Action<BindingEntry> completed)
{
Contract.Requires<ArgumentNullException>(binding != null);
Contract.Requires<ArgumentNullException>(changed != null);
Contract.Requires<ArgumentNullException>(completed != null);
if (this.subscription != null)
{
throw new Exception("PriorityValue.Entry.Start() called more than once.");
}
this.Priority = priority;
this.Value = PerspexProperty.UnsetValue;
if (binding is IDescription)
{
this.Description = ((IDescription)binding).Description;
}
this.subscription = binding.Subscribe(
value =>
{
this.Value = value;
changed(this);
},
() => completed(this));
}
/// <summary>
/// Ends the binding subscription.
/// </summary>
public void Dispose()
{
if (this.subscription != null)
{
this.subscription.Dispose();
}
}
}
}
}

30
Perspex.Base/Properties/AssemblyInfo.cs

@ -0,0 +1,30 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Perspex.Base")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Perspex.Base")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

102
Perspex.Base/Threading/Dispatcher.cs

@ -0,0 +1,102 @@
// -----------------------------------------------------------------------
// <copyright file="Dispatcher.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Threading
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Security;
using System.Threading;
using Perspex.Platform;
using Splat;
public enum DispatcherPriority
{
Invalid = -1,
Inactive = 0,
SystemIdle = 1,
ApplicationIdle = 2,
ContextIdle = 3,
Background = 4,
Input = 5,
Loaded = 6,
Render = 7,
DataBind = 8,
Normal = 9,
Send = 10,
}
[Flags]
internal enum Flags
{
ShutdownStarted = 1,
Shutdown = 2,
Disabled = 4
}
public abstract class Dispatcher
{
private static DispatcherFrame mainExecutionFrame = new DispatcherFrame();
public static Dispatcher CurrentDispatcher
{
get { return Locator.Current.GetService<IPlatformThreadingInterface>().GetThreadDispatcher(); }
}
public abstract bool HasShutdownFinished
{
get;
}
public abstract DispatcherFrame CurrentFrame
{
get;
set;
}
public static void PushFrame(DispatcherFrame frame)
{
if (frame == null)
{
throw new ArgumentNullException("frame");
}
Dispatcher dis = CurrentDispatcher;
if (dis.HasShutdownFinished)
{
throw new InvalidOperationException("The Dispatcher has shut down");
}
if (frame.Running != null)
{
throw new InvalidOperationException("Frame is already running on a different dispatcher");
}
frame.ParentFrame = dis.CurrentFrame;
dis.CurrentFrame = frame;
frame.Running = dis;
dis.RunFrame(frame);
}
public static void Run()
{
PushFrame(mainExecutionFrame);
}
public abstract DispatcherOperation BeginInvoke(Action method);
public abstract DispatcherOperation BeginInvoke(DispatcherPriority priority, Action method);
protected internal abstract void Reprioritize(DispatcherOperation op, DispatcherPriority oldpriority);
protected abstract void RunFrame(DispatcherFrame frame);
}
}

25
Perspex.Base/Threading/DispatcherFrame.cs

@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="DispatcherFrame.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Threading
{
public class DispatcherFrame
{
public DispatcherFrame()
{
this.Continue = true;
this.ExitOnRequest = true;
}
public bool Continue { get; set; }
public bool ExitOnRequest { get; set; }
public Dispatcher Running { get; set; }
public DispatcherFrame ParentFrame { get; set; }
}
}

159
Perspex.Base/Threading/DispatcherOperation.cs

@ -0,0 +1,159 @@
// -----------------------------------------------------------------------
// <copyright file="DispatcherOperation.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Threading
{
using System;
using System.Security;
public enum DispatcherOperationStatus
{
Pending = 0,
Aborted = 1,
Completed = 2,
Executing = 3
}
public sealed class DispatcherOperation
{
private DispatcherOperationStatus status;
private DispatcherPriority priority;
private Dispatcher dispatcher;
private object result;
private Delegate delegateMethod;
private object[] delegateArgs;
public DispatcherOperation(Dispatcher dis, DispatcherPriority prio)
{
this.dispatcher = dis;
this.priority = prio;
if (this.Dispatcher.HasShutdownFinished)
{
this.status = DispatcherOperationStatus.Aborted;
}
else
{
this.status = DispatcherOperationStatus.Pending;
}
}
public DispatcherOperation(Dispatcher dis, DispatcherPriority prio, Delegate d)
: this(dis, prio)
{
this.delegateMethod = d;
}
public DispatcherOperation(Dispatcher dis, DispatcherPriority prio, Delegate d, object arg)
: this(dis, prio)
{
this.delegateMethod = d;
this.delegateArgs = new object[1];
this.delegateArgs[0] = arg;
}
public DispatcherOperation(Dispatcher dis, DispatcherPriority prio, Delegate d, object arg, object[] args)
: this(dis, prio)
{
this.delegateMethod = d;
this.delegateArgs = new object[args.Length + 1];
this.delegateArgs[0] = arg;
Array.Copy(args, 1, this.delegateArgs, 0, args.Length);
}
public event EventHandler Completed;
public DispatcherOperationStatus Status
{
get
{
return this.status;
}
internal set
{
this.status = value;
}
}
public Dispatcher Dispatcher
{
get
{
return this.dispatcher;
}
}
public DispatcherPriority Priority
{
get
{
return this.priority;
}
set
{
if (this.priority != value)
{
DispatcherPriority old = this.priority;
this.priority = value;
this.dispatcher.Reprioritize(this, old);
}
}
}
public object Result
{
get
{
return this.result;
}
}
public bool Abort()
{
this.status = DispatcherOperationStatus.Aborted;
throw new NotImplementedException();
}
public DispatcherOperationStatus Wait()
{
if (this.status == DispatcherOperationStatus.Executing)
{
throw new InvalidOperationException("Already executing");
}
throw new NotImplementedException();
}
[SecurityCritical]
public DispatcherOperationStatus Wait(TimeSpan timeout)
{
if (this.status == DispatcherOperationStatus.Executing)
{
throw new InvalidOperationException("Already executing");
}
throw new NotImplementedException();
}
public void Invoke()
{
this.status = DispatcherOperationStatus.Executing;
if (this.delegateMethod != null)
{
this.result = this.delegateMethod.DynamicInvoke(this.delegateArgs);
}
this.status = DispatcherOperationStatus.Completed;
if (this.Completed != null)
{
this.Completed(this, EventArgs.Empty);
}
}
}
}

140
Perspex.Base/Threading/DispatcherTimer.cs

@ -0,0 +1,140 @@
// -----------------------------------------------------------------------
// <copyright file="DispatcherTimer.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Threading
{
using System;
using Perspex.Platform;
using Splat;
public class DispatcherTimer
{
private object timerHandle;
private DispatcherPriority priority;
private TimeSpan interval;
public DispatcherTimer()
{
this.priority = DispatcherPriority.Normal;
this.Dispatcher = Dispatcher.CurrentDispatcher;
}
public DispatcherTimer(DispatcherPriority priority)
{
this.priority = priority;
this.Dispatcher = Dispatcher.CurrentDispatcher;
}
public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher)
{
this.priority = priority;
this.Dispatcher = dispatcher;
}
public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback, Dispatcher dispatcher)
{
this.priority = priority;
this.Dispatcher = dispatcher;
this.Interval = interval;
this.Tick += callback;
}
~DispatcherTimer()
{
if (this.timerHandle != null)
{
this.Stop();
}
}
public event EventHandler Tick;
public Dispatcher Dispatcher
{
get;
private set;
}
public TimeSpan Interval
{
get
{
return this.interval;
}
set
{
bool enabled = this.IsEnabled;
this.Stop();
this.interval = value;
this.IsEnabled = enabled;
}
}
public bool IsEnabled
{
get
{
return this.timerHandle != null;
}
set
{
if (this.IsEnabled != value)
{
if (value)
{
this.Start();
}
else
{
this.Stop();
}
}
}
}
public object Tag
{
get;
set;
}
public void Start()
{
if (!this.IsEnabled)
{
IPlatformThreadingInterface threading = Locator.Current.GetService<IPlatformThreadingInterface>();
this.timerHandle = threading.StartTimer(this.Interval, this.InternalTick);
}
}
public void Stop()
{
if (this.IsEnabled)
{
IPlatformThreadingInterface threading = Locator.Current.GetService<IPlatformThreadingInterface>();
threading.KillTimer(this.timerHandle);
this.timerHandle = null;
}
}
private void InternalTick()
{
this.Dispatcher.BeginInvoke(this.priority, (Action)this.RaiseTick);
}
private void RaiseTick()
{
if (this.Tick != null)
{
this.Tick(this, EventArgs.Empty);
}
}
}
}

10
Perspex.Base/packages.config

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Interfaces" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Linq" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Main" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-PlatformServices" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Splat" version="1.3.3" targetFramework="portable-net45+win" />
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="portable-net45+win" developmentDependency="true" />
</packages>

7
Perspex.UnitTests/Controls/ContentControlTests.cs → Perspex.Controls.UnitTests/ContentControlTests.cs

@ -4,7 +4,7 @@
// </copyright> // </copyright>
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
namespace Perspex.UnitTests.Controls namespace Perspex.Controls.UnitTests
{ {
using System.Linq; using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -12,7 +12,6 @@ namespace Perspex.UnitTests.Controls
using Perspex.Controls; using Perspex.Controls;
using Perspex.Layout; using Perspex.Layout;
using Perspex.Styling; using Perspex.Styling;
using Perspex.Themes.Default;
using Splat; using Splat;
[TestClass] [TestClass]
@ -116,7 +115,7 @@ namespace Perspex.UnitTests.Controls
target.Content = child; target.Content = child;
this.ApplyTemplate(target); this.ApplyTemplate(target);
var contentPresenter = (ContentPresenter)((IVisual)child).VisualParent; var contentPresenter = child.GetVisualParent<ContentPresenter>();
Assert.AreEqual(target, contentPresenter.TemplatedParent); Assert.AreEqual(target, contentPresenter.TemplatedParent);
} }
@ -145,7 +144,7 @@ namespace Perspex.UnitTests.Controls
Border border = new Border(); Border border = new Border();
border.Background = new Perspex.Media.SolidColorBrush(0xffffffff); border.Background = new Perspex.Media.SolidColorBrush(0xffffffff);
ContentPresenter contentPresenter = new ContentPresenter(); ContentPresenter contentPresenter = new ContentPresenter();
contentPresenter.TemplateBinding(parent, ContentPresenter.ContentProperty); ////contentPresenter[~ContentPresenter.ContentProperty] = parent[~ContentPresenter.ContentProperty];
border.Content = contentPresenter; border.Content = contentPresenter;
return border; return border;
}); });

23
Perspex.UnitTests/Controls/ControlTests.cs → Perspex.Controls.UnitTests/ControlTests.cs

@ -4,7 +4,7 @@
// </copyright> // </copyright>
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
namespace Perspex.UnitTests.Controls namespace Perspex.Controls.UnitTests
{ {
using System.Linq; using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -25,27 +25,6 @@ namespace Perspex.UnitTests.Controls
Assert.AreEqual(0, target.Classes.Count); Assert.AreEqual(0, target.Classes.Count);
} }
[TestMethod]
public void Setting_IsPointerOver_Should_Add_PointerOver_Class()
{
var target = new Control();
target.IsPointerOver = true;
CollectionAssert.AreEqual(new[] { ":pointerover" }, target.Classes.ToArray());
}
[TestMethod]
public void Clearing_IsPointerOver_Should_Remove_PointerOver_Class()
{
var target = new Control();
target.IsPointerOver = true;
target.IsPointerOver = false;
Assert.AreEqual(0, target.Classes.Count);
}
[TestMethod] [TestMethod]
public void Adding_Control_To_ILayoutRoot_Should_Style_Control() public void Adding_Control_To_ILayoutRoot_Should_Style_Control()
{ {

125
Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex.Controls.UnitTests</RootNamespace>
<AssemblyName>Perspex.Controls.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.2.1312.1622\lib\net40\Moq.dll</HintPath>
</Reference>
<Reference Include="Splat">
<HintPath>..\packages\Splat.1.3.3\lib\Net45\Splat.dll</HintPath>
</Reference>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="ContentControlTests.cs" />
<Compile Include="ControlTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TemplatedControlTests.cs" />
<Compile Include="TestRoot.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Controls\Perspex.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Input\Perspex.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Perspex.Input</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

36
Perspex.Controls.UnitTests/Properties/AssemblyInfo.cs

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Perspex.Controls.UnitTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Perspex.Controls.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("0430478f-6126-4390-b94b-c02d91fd4f7c")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

2
Perspex.UnitTests/Controls/TemplatedControlTests.cs → Perspex.Controls.UnitTests/TemplatedControlTests.cs

@ -4,7 +4,7 @@
// </copyright> // </copyright>
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
namespace Perspex.UnitTests.Controls namespace Perspex.Controls.UnitTests
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;

6
Perspex.UnitTests/TestRoot.cs → Perspex.Controls.UnitTests/TestRoot.cs

@ -4,13 +4,9 @@
// </copyright> // </copyright>
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
namespace Perspex.UnitTests namespace Perspex.Controls.UnitTests
{ {
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Moq; using Moq;
using Perspex.Controls;
using Perspex.Layout; using Perspex.Layout;
internal class TestRoot : Decorator, ILayoutRoot internal class TestRoot : Decorator, ILayoutRoot

5
Perspex.Controls.UnitTests/packages.config

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Moq" version="4.2.1312.1622" targetFramework="net45" />
<package id="Splat" version="1.3.3" targetFramework="net45" />
</packages>

62
Perspex.Controls/Border.cs

@ -0,0 +1,62 @@
// -----------------------------------------------------------------------
// <copyright file="Border.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Reactive.Linq;
using Perspex.Layout;
using Perspex.Media;
public class Border : Decorator
{
static Border()
{
AffectsRender(BackgroundProperty);
AffectsRender(BorderBrushProperty);
}
public override void Render(IDrawingContext context)
{
Brush background = this.Background;
Brush borderBrush = this.BorderBrush;
double borderThickness = this.BorderThickness;
Rect rect = new Rect(this.ActualSize).Deflate(BorderThickness / 2);
if (background != null)
{
context.FillRectange(background, rect);
}
if (borderBrush != null && borderThickness > 0)
{
context.DrawRectange(new Pen(borderBrush, borderThickness), rect);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
Control content = this.Content;
if (content != null)
{
Thickness padding = this.Padding + new Thickness(this.BorderThickness);
content.Arrange(new Rect(finalSize).Deflate(padding));
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
return LayoutHelper.MeasureDecorator(
this,
this.Content,
availableSize,
this.Padding + new Thickness(this.BorderThickness));
}
}
}

49
Perspex.Controls/Button.cs

@ -0,0 +1,49 @@
// -----------------------------------------------------------------------
// <copyright file="Button.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using Perspex.Interactivity;
public class Button : ContentControl
{
public static readonly RoutedEvent<RoutedEventArgs> ClickEvent =
RoutedEvent.Register<Button, RoutedEventArgs>("Click", RoutingStrategy.Bubble);
static Button()
{
FocusableProperty.OverrideDefaultValue(typeof(Button), true);
}
public Button()
{
this.PointerPressed += (s, e) =>
{
this.Classes.Add(":pressed");
e.Device.Capture(this);
};
this.PointerReleased += (s, e) =>
{
e.Device.Capture(null);
this.Classes.Remove(":pressed");
if (this.Classes.Contains(":pointerover"))
{
RoutedEventArgs click = new RoutedEventArgs(ClickEvent, this);
this.RaiseEvent(click);
}
};
}
public event EventHandler<RoutedEventArgs> Click
{
add { this.AddHandler(ClickEvent, value); }
remove { this.RemoveHandler(ClickEvent, value); }
}
}
}

12
Perspex.Controls/CheckBox.cs

@ -0,0 +1,12 @@
// -----------------------------------------------------------------------
// <copyright file="CheckBox.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public class CheckBox : ToggleButton
{
}
}

53
Perspex.Controls/ColumnDefinition.cs

@ -0,0 +1,53 @@
// -----------------------------------------------------------------------
// <copyright file="ColumnDefinition.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public class ColumnDefinition : DefinitionBase
{
public static readonly PerspexProperty<double> MaxWidthProperty =
PerspexProperty.Register<ColumnDefinition, double>("MaxWidth", double.PositiveInfinity);
public static readonly PerspexProperty<double> MinWidthProperty =
PerspexProperty.Register<ColumnDefinition, double>("MinWidth");
public static readonly PerspexProperty<GridLength> WidthProperty =
PerspexProperty.Register<ColumnDefinition, GridLength>("Width", new GridLength(1, GridUnitType.Star));
public ColumnDefinition()
{
}
public ColumnDefinition(GridLength width)
{
this.Width = width;
}
public double ActualWidth
{
get;
internal set;
}
public double MaxWidth
{
get { return this.GetValue(MaxWidthProperty); }
set { this.SetValue(MaxWidthProperty, value); }
}
public double MinWidth
{
get { return this.GetValue(MinWidthProperty); }
set { this.SetValue(MinWidthProperty, value); }
}
public GridLength Width
{
get { return this.GetValue(WidthProperty); }
set { this.SetValue(WidthProperty, value); }
}
}
}

12
Perspex.Controls/ColumnDefinitions.cs

@ -0,0 +1,12 @@
// -----------------------------------------------------------------------
// <copyright file="ColumnDefinitions.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public class ColumnDefinitions : PerspexList<ColumnDefinition>
{
}
}

24
Perspex.Controls/ContentControl.cs

@ -0,0 +1,24 @@
// -----------------------------------------------------------------------
// <copyright file="ContentControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
public class ContentControl : TemplatedControl
{
public static readonly PerspexProperty<object> ContentProperty =
PerspexProperty.Register<ContentControl, object>("Content");
public object Content
{
get { return this.GetValue(ContentProperty); }
set { this.SetValue(ContentProperty, value); }
}
}
}

151
Perspex.Controls/ContentPresenter.cs

@ -0,0 +1,151 @@
// -----------------------------------------------------------------------
// <copyright file="ContentPresenter.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Layout;
using Perspex.Media;
using Perspex.Styling;
public class ContentPresenter : Control, IVisual
{
public static readonly PerspexProperty<object> ContentProperty =
ContentControl.ContentProperty.AddOwner<Control>();
public static readonly PerspexProperty<Func<object, Visual>> DataTemplateProperty =
PerspexProperty.Register<ContentPresenter, Func<object, Visual>>("DataTemplate");
public ContentPresenter()
{
this.GetObservableWithHistory(ContentProperty).Subscribe(this.ContentChanged);
}
public object Content
{
get { return this.GetValue(ContentProperty); }
set { this.SetValue(ContentProperty, value); }
}
protected override IEnumerable<Visual> CreateVisualChildren()
{
object content = this.Content;
if (content != null)
{
Control result;
if (content is Control)
{
result = (Control)content;
}
else
{
DataTemplate dataTemplate = this.FindDataTemplate(content);
if (dataTemplate != null)
{
result = dataTemplate.Build(content);
}
else
{
result = new TextBlock
{
Text = content.ToString(),
};
}
}
result.TemplatedParent = null;
return Enumerable.Repeat(result, 1);
}
else
{
return Enumerable.Empty<Visual>();
}
}
protected override Size ArrangeOverride(Size finalSize)
{
Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control;
if (child != null)
{
double left;
double top;
double width;
double height;
switch (child.HorizontalAlignment)
{
case HorizontalAlignment.Left:
left = 0;
width = child.DesiredSize.Value.Width;
break;
case HorizontalAlignment.Center:
left = (finalSize.Width / 2) - (child.DesiredSize.Value.Width / 2);
width = child.DesiredSize.Value.Width;
break;
case HorizontalAlignment.Right:
left = finalSize.Width - child.DesiredSize.Value.Width;
width = child.DesiredSize.Value.Width;
break;
default:
left = 0;
width = finalSize.Width;
break;
}
switch (child.VerticalAlignment)
{
case VerticalAlignment.Top:
top = 0;
height = child.DesiredSize.Value.Height;
break;
case VerticalAlignment.Center:
top = (finalSize.Height / 2) - (child.DesiredSize.Value.Height / 2);
height = child.DesiredSize.Value.Height;
break;
case VerticalAlignment.Bottom:
top = finalSize.Height - child.DesiredSize.Value.Height;
height = child.DesiredSize.Value.Height;
break;
default:
top = 0;
height = finalSize.Height;
break;
}
child.Arrange(new Rect(left, top, width, height));
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control;
if (child != null)
{
child.Measure(availableSize);
return child.DesiredSize.Value;
}
return new Size();
}
private void ContentChanged(Tuple<object, object> content)
{
var children = this.CreateVisualChildren();
this.ClearVisualChildren();
this.AddVisualChildren(children);
this.InvalidateMeasure();
}
}
}

270
Perspex.Controls/Control.cs

@ -0,0 +1,270 @@
// -----------------------------------------------------------------------
// <copyright file="Control.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Input;
using Perspex.Media;
using Perspex.Rendering;
using Perspex.Styling;
using Splat;
public class Control : InputElement, ILogical, IStyleable, IStyleHost
{
public static readonly PerspexProperty<Brush> BackgroundProperty =
PerspexProperty.Register<Control, Brush>("Background", inherits: true);
public static readonly PerspexProperty<Brush> BorderBrushProperty =
PerspexProperty.Register<Control, Brush>("BorderBrush");
public static readonly PerspexProperty<double> BorderThicknessProperty =
PerspexProperty.Register<Control, double>("BorderThickness");
public static readonly PerspexProperty<double> FontSizeProperty =
PerspexProperty.Register<Control, double>(
"FontSize",
defaultValue: 12.0,
inherits: true);
public static readonly PerspexProperty<Brush> ForegroundProperty =
PerspexProperty.Register<Control, Brush>("Foreground", new SolidColorBrush(0xff000000), true);
public static readonly PerspexProperty<Control> ParentProperty =
PerspexProperty.Register<Control, Control>("Parent");
public static readonly PerspexProperty<ITemplatedControl> TemplatedParentProperty =
PerspexProperty.Register<Control, ITemplatedControl>("TemplatedParent", inherits: true);
private Classes classes;
private DataTemplates dataTemplates;
private string id;
private Styles styles;
static Control()
{
AffectsMeasure(IsVisibleProperty);
}
public Control()
{
this.classes = new Classes();
this.AddPseudoClass(IsPointerOverProperty, ":pointerover");
this.AddPseudoClass(IsFocusedProperty, ":focus");
}
public Brush Background
{
get { return this.GetValue(BackgroundProperty); }
set { this.SetValue(BackgroundProperty, value); }
}
public Brush BorderBrush
{
get { return this.GetValue(BorderBrushProperty); }
set { this.SetValue(BorderBrushProperty, value); }
}
public double BorderThickness
{
get { return this.GetValue(BorderThicknessProperty); }
set { this.SetValue(BorderThicknessProperty, value); }
}
public Classes Classes
{
get
{
return this.classes;
}
set
{
if (this.classes != value)
{
this.classes.Clear();
this.classes.Add(value);
}
}
}
public DataTemplates DataTemplates
{
get
{
if (this.dataTemplates == null)
{
this.dataTemplates = new DataTemplates();
}
return this.dataTemplates;
}
set
{
this.dataTemplates = value;
}
}
public double FontSize
{
get { return this.GetValue(FontSizeProperty); }
set { this.SetValue(FontSizeProperty, value); }
}
public Brush Foreground
{
get { return this.GetValue(ForegroundProperty); }
set { this.SetValue(ForegroundProperty, value); }
}
public string Id
{
get
{
return this.id;
}
set
{
if (this.id != null)
{
throw new InvalidOperationException("ID already set.");
}
if (((IVisual)this).VisualParent != null)
{
throw new InvalidOperationException("Cannot set ID : control already added to tree.");
}
this.id = value;
}
}
public Control Parent
{
get { return this.GetValue(ParentProperty); }
protected set { this.SetValue(ParentProperty, value); }
}
public Styles Styles
{
get
{
if (this.styles == null)
{
this.styles = new Styles();
}
return this.styles;
}
set
{
this.styles = value;
}
}
public ITemplatedControl TemplatedParent
{
get { return this.GetValue(TemplatedParentProperty); }
internal set { this.SetValue(TemplatedParentProperty, value); }
}
ILogical ILogical.LogicalParent
{
get { return this.Parent; }
set { this.Parent = (Control)value; }
}
IEnumerable<ILogical> ILogical.LogicalChildren
{
get { return Enumerable.Empty<ILogical>(); }
}
protected override void OnAttachedToVisualTree(IRenderRoot root)
{
IStyler styler = Locator.Current.GetService<IStyler>();
styler.ApplyStyles(this);
}
protected void AddPseudoClass(PerspexProperty<bool> property, string className)
{
this.GetObservable(property).Subscribe(x =>
{
if (x)
{
this.classes.Add(className);
}
else
{
this.classes.Remove(className);
}
});
}
protected virtual DataTemplate FindDataTemplate(object content)
{
Control control = content as Control;
if (control != null)
{
return new DataTemplate(x => control);
}
ILogical node = this;
while (node != null)
{
control = node as Control;
if (control != null)
{
foreach (DataTemplate dt in control.DataTemplates.Reverse())
{
if (dt.Match(content))
{
return dt;
}
}
}
node = node.LogicalParent;
if (node == null && control != null)
{
node = control.TemplatedParent as ILogical;
}
}
IGlobalDataTemplates global = Locator.Current.GetService<IGlobalDataTemplates>();
if (global != null)
{
foreach (DataTemplate dt in global.Reverse())
{
if (dt.Match(content))
{
return dt;
}
}
}
return null;
}
protected DataTemplate GetDataTemplate(object content)
{
return this.FindDataTemplate(content) ?? DataTemplate.Default;
}
}
}

37
Perspex.Controls/ControlExtensions.cs

@ -0,0 +1,37 @@
// -----------------------------------------------------------------------
// <copyright file="ControlExtensions.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Controls;
using Perspex.Styling;
public static class ControlExtensions
{
public static IEnumerable<Control> GetTemplateControls(this ITemplatedControl control)
{
return GetTemplateControls(control, (IVisual)control);
}
public static IEnumerable<Control> GetTemplateControls(ITemplatedControl templated, IVisual parent)
{
IVisual visual = parent as IVisual;
foreach (IVisual child in visual.VisualChildren.OfType<Control>().Where(x => x.TemplatedParent == templated))
{
yield return (Control)child;
foreach (IVisual grandchild in GetTemplateControls(templated, child))
{
yield return (Control)grandchild;
}
}
}
}
}

40
Perspex.Controls/ControlTemplate.cs

@ -0,0 +1,40 @@
// -----------------------------------------------------------------------
// <copyright file="ControlTemplate.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using Perspex.Styling;
public class ControlTemplate
{
private Func<ITemplatedControl, Control> build;
public ControlTemplate(Func<ITemplatedControl, Control> build)
{
Contract.Requires<NullReferenceException>(build != null);
this.build = build;
}
public static ControlTemplate Create<TControl>(Func<TControl, Control> build)
where TControl : ITemplatedControl
{
Contract.Requires<NullReferenceException>(build != null);
return new ControlTemplate(c => build((TControl)c));
}
public Control Build(ITemplatedControl templatedParent)
{
Contract.Requires<NullReferenceException>(templatedParent != null);
Control root = this.build(templatedParent);
root.TemplatedParent = templatedParent;
return root;
}
}
}

22
Perspex.Controls/Controls.cs

@ -0,0 +1,22 @@
// -----------------------------------------------------------------------
// <copyright file="Controls.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
using System.Collections.Generic;
namespace Perspex.Controls
{
public class Controls : PerspexList<Control>
{
public Controls()
{
}
public Controls(IEnumerable<Control> items)
: base(items)
{
}
}
}

68
Perspex.Controls/DataTemplate.cs

@ -0,0 +1,68 @@
// -----------------------------------------------------------------------
// <copyright file="DataTemplate.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Reflection;
public class DataTemplate
{
public static readonly DataTemplate Default =
new DataTemplate(typeof(object), o => new TextBlock { Text = o.ToString() });
public DataTemplate(Func<object, Control> build)
: this(o => true, build)
{
}
public DataTemplate(Type type, Func<object, Control> build)
: this(o => IsInstance(o, type), build)
{
}
public DataTemplate(Func<object, bool> match, Func<object, Control> build)
{
Contract.Requires<ArgumentNullException>(match != null);
Contract.Requires<ArgumentNullException>(build != null);
this.Match = match;
this.Build = build;
}
public static bool IsInstance(object o, Type t)
{
return t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
}
public Func<object, bool> Match { get; private set; }
public Func<object, Control> Build { get; private set; }
}
public class DataTemplate<T> : DataTemplate
{
public DataTemplate(Func<T, Control> build)
: base(typeof(T), Cast(build))
{
}
public DataTemplate(Func<T, bool> match, Func<T, Control> build)
: base(CastMatch(match), Cast(build))
{
}
private static Func<object, bool> CastMatch(Func<T, bool> f)
{
return o => (o is T) ? f((T)o) : false;
}
private static Func<object, TResult> Cast<TResult>(Func<T, TResult> f)
{
return o => f((T)o);
}
}
}

20
Perspex.Controls/DataTemplates.cs

@ -0,0 +1,20 @@
// -----------------------------------------------------------------------
// <copyright file="DataTemplates.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive;
using System.Reactive.Subjects;
public class DataTemplates : PerspexList<DataTemplate>
{
}
}

65
Perspex.Controls/Decorator.cs

@ -0,0 +1,65 @@
// -----------------------------------------------------------------------
// <copyright file="Decorator.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Layout;
public class Decorator : Control, IVisual
{
public static readonly PerspexProperty<Control> ContentProperty =
PerspexProperty.Register<Decorator, Control>("Content");
public static readonly PerspexProperty<Thickness> PaddingProperty =
PerspexProperty.Register<Decorator, Thickness>("Padding");
public Decorator()
{
this.GetObservable(ContentProperty).Subscribe(x =>
{
this.ClearVisualChildren();
if (x != null)
{
this.AddVisualChild(x);
}
});
}
public Control Content
{
get { return this.GetValue(ContentProperty); }
set { this.SetValue(ContentProperty, value); }
}
public Thickness Padding
{
get { return this.GetValue(PaddingProperty); }
set { this.SetValue(PaddingProperty, value); }
}
protected override Size ArrangeOverride(Size finalSize)
{
Control content = this.Content;
if (content != null)
{
content.Arrange(new Rect(finalSize).Deflate(this.Padding));
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
return LayoutHelper.MeasureDecorator(this, this.Content, availableSize, this.Padding);
}
}
}

20
Perspex.Controls/DefinitionBase.cs

@ -0,0 +1,20 @@
// -----------------------------------------------------------------------
// <copyright file="DefinitionBase.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public class DefinitionBase : PerspexObject
{
public static readonly PerspexProperty<string> SharedSizeGroupProperty =
PerspexProperty.Register<DefinitionBase, string>("SharedSizeGroup", inherits: true);
public string SharedSizeGroup
{
get { return this.GetValue(SharedSizeGroupProperty); }
set { this.SetValue(SharedSizeGroupProperty, value); }
}
}
}

805
Perspex.Controls/Grid.cs

@ -0,0 +1,805 @@
// -----------------------------------------------------------------------
// <copyright file="Grid.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
public class Grid : Panel
{
public static readonly PerspexProperty<int> ColumnProperty =
PerspexProperty.RegisterAttached<Grid, Control, int>("Column");
public static readonly PerspexProperty<int> ColumnSpanProperty =
PerspexProperty.RegisterAttached<Grid, Control, int>("ColumnSpan", 1);
public static readonly PerspexProperty<bool> IsSharedSizeScopeProperty =
PerspexProperty.RegisterAttached<Grid, Control, bool>("IsSharedSizeScopeProperty");
public static readonly PerspexProperty<int> RowProperty =
PerspexProperty.RegisterAttached<Grid, Control, int>("Row");
public static readonly PerspexProperty<int> RowSpanProperty =
PerspexProperty.RegisterAttached<Grid, Control, int>("RowSpan", 1);
private Segment[,] rowMatrix;
private Segment[,] colMatrix;
public Grid()
{
this.ColumnDefinitions = new ColumnDefinitions();
this.RowDefinitions = new RowDefinitions();
}
public ColumnDefinitions ColumnDefinitions
{
get;
set;
}
public RowDefinitions RowDefinitions
{
get;
set;
}
public static int GetColumn(PerspexObject element)
{
return element.GetValue(ColumnProperty);
}
public static int GetColumnSpan(PerspexObject element)
{
return element.GetValue(ColumnSpanProperty);
}
public static int GetRow(PerspexObject element)
{
return element.GetValue(RowProperty);
}
public static int GetRowSpan(PerspexObject element)
{
return element.GetValue(RowSpanProperty);
}
public static void SetColumn(PerspexObject element, int value)
{
element.SetValue(ColumnProperty, value);
}
public static void SetColumnSpan(PerspexObject element, int value)
{
element.SetValue(ColumnSpanProperty, value);
}
public static void SetRow(PerspexObject element, int value)
{
element.SetValue(RowProperty, value);
}
public static void SetRowSpan(PerspexObject element, int value)
{
element.SetValue(RowSpanProperty, value);
}
protected override Size MeasureOverride(Size constraint)
{
Size totalSize = constraint;
int colCount = this.ColumnDefinitions.Count;
int rowCount = this.RowDefinitions.Count;
double totalStarsX = 0;
double totalStarsY = 0;
bool emptyRows = rowCount == 0;
bool emptyCols = colCount == 0;
bool hasChildren = this.Children.Count > 0;
if (emptyRows)
{
rowCount = 1;
}
if (emptyCols)
{
colCount = 1;
}
this.CreateMatrices(rowCount, colCount);
if (emptyRows)
{
this.rowMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star);
this.rowMatrix[0, 0].Stars = 1.0;
totalStarsY += 1.0;
}
else
{
for (int i = 0; i < rowCount; i++)
{
RowDefinition rowdef = this.RowDefinitions[i];
GridLength height = rowdef.Height;
rowdef.ActualHeight = double.PositiveInfinity;
this.rowMatrix[i, i] = new Segment(0, rowdef.MinHeight, rowdef.MaxHeight, height.GridUnitType);
if (height.GridUnitType == GridUnitType.Pixel)
{
this.rowMatrix[i, i].OfferedSize = Clamp(height.Value, this.rowMatrix[i, i].Min, this.rowMatrix[i, i].Max);
this.rowMatrix[i, i].DesiredSize = this.rowMatrix[i, i].OfferedSize;
rowdef.ActualHeight = this.rowMatrix[i, i].OfferedSize;
}
else if (height.GridUnitType == GridUnitType.Star)
{
this.rowMatrix[i, i].Stars = height.Value;
totalStarsY += height.Value;
}
else if (height.GridUnitType == GridUnitType.Auto)
{
this.rowMatrix[i, i].OfferedSize = Clamp(0, this.rowMatrix[i, i].Min, this.rowMatrix[i, i].Max);
this.rowMatrix[i, i].DesiredSize = this.rowMatrix[i, i].OfferedSize;
}
}
}
if (emptyCols)
{
this.colMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star);
this.colMatrix[0, 0].Stars = 1.0;
totalStarsX += 1.0;
}
else
{
for (int i = 0; i < colCount; i++)
{
ColumnDefinition coldef = this.ColumnDefinitions[i];
GridLength width = coldef.Width;
coldef.ActualWidth = double.PositiveInfinity;
this.colMatrix[i, i] = new Segment(0, coldef.MinWidth, coldef.MaxWidth, width.GridUnitType);
if (width.GridUnitType == GridUnitType.Pixel)
{
this.colMatrix[i, i].OfferedSize = Clamp(width.Value, this.colMatrix[i, i].Min, this.colMatrix[i, i].Max);
this.colMatrix[i, i].DesiredSize = this.colMatrix[i, i].OfferedSize;
coldef.ActualWidth = this.colMatrix[i, i].OfferedSize;
}
else if (width.GridUnitType == GridUnitType.Star)
{
this.colMatrix[i, i].Stars = width.Value;
totalStarsX += width.Value;
}
else if (width.GridUnitType == GridUnitType.Auto)
{
this.colMatrix[i, i].OfferedSize = Clamp(0, this.colMatrix[i, i].Min, this.colMatrix[i, i].Max);
this.colMatrix[i, i].DesiredSize = this.colMatrix[i, i].OfferedSize;
}
}
}
List<GridNode> sizes = new List<GridNode>();
GridNode node;
GridNode separator = new GridNode(null, 0, 0, 0);
int separatorIndex;
sizes.Add(separator);
// Pre-process the grid children so that we know what types of elements we have so
// we can apply our special measuring rules.
GridWalker gridWalker = new GridWalker(this, this.rowMatrix, this.colMatrix);
for (int i = 0; i < 6; i++)
{
// These bools tell us which grid element type we should be measuring. i.e.
// 'star/auto' means we should measure elements with a star row and auto col
bool autoAuto = i == 0;
bool starAuto = i == 1;
bool autoStar = i == 2;
bool starAutoAgain = i == 3;
bool nonStar = i == 4;
bool remainingStar = i == 5;
if (hasChildren)
{
this.ExpandStarCols(totalSize);
this.ExpandStarRows(totalSize);
}
foreach (Control child in this.Children)
{
int col, row;
int colspan, rowspan;
double childSizeX = 0;
double childSizeY = 0;
bool starCol = false;
bool starRow = false;
bool autoCol = false;
bool autoRow = false;
col = Math.Min(GetColumn(child), colCount - 1);
row = Math.Min(GetRow(child), rowCount - 1);
colspan = Math.Min(GetColumnSpan(child), colCount - col);
rowspan = Math.Min(GetRowSpan(child), rowCount - row);
for (int r = row; r < row + rowspan; r++)
{
starRow |= this.rowMatrix[r, r].Type == GridUnitType.Star;
autoRow |= this.rowMatrix[r, r].Type == GridUnitType.Auto;
}
for (int c = col; c < col + colspan; c++)
{
starCol |= this.colMatrix[c, c].Type == GridUnitType.Star;
autoCol |= this.colMatrix[c, c].Type == GridUnitType.Auto;
}
// This series of if statements checks whether or not we should measure
// the current element and also if we need to override the sizes
// passed to the Measure call.
// If the element has Auto rows and Auto columns and does not span Star
// rows/cols it should only be measured in the auto_auto phase.
// There are similar rules governing auto/star and star/auto elements.
// NOTE: star/auto elements are measured twice. The first time with
// an override for height, the second time without it.
if (autoRow && autoCol && !starRow && !starCol)
{
if (!autoAuto)
{
continue;
}
childSizeX = double.PositiveInfinity;
childSizeY = double.PositiveInfinity;
}
else if (starRow && autoCol && !starCol)
{
if (!(starAuto || starAutoAgain))
{
continue;
}
if (starAuto && gridWalker.HasAutoStar)
{
childSizeY = double.PositiveInfinity;
}
childSizeX = double.PositiveInfinity;
}
else if (autoRow && starCol && !starRow)
{
if (!autoStar)
{
continue;
}
childSizeY = double.PositiveInfinity;
}
else if ((autoRow || autoCol) && !(starRow || starCol))
{
if (!nonStar)
{
continue;
}
if (autoRow)
{
childSizeY = double.PositiveInfinity;
}
if (autoCol)
{
childSizeX = double.PositiveInfinity;
}
}
else if (!(starRow || starCol))
{
if (!nonStar)
{
continue;
}
}
else
{
if (!remainingStar)
{
continue;
}
}
for (int r = row; r < row + rowspan; r++)
{
childSizeY += this.rowMatrix[r, r].OfferedSize;
}
for (int c = col; c < col + colspan; c++)
{
childSizeX += this.colMatrix[c, c].OfferedSize;
}
child.Measure(new Size(childSizeX, childSizeY));
Size desired = child.DesiredSize.Value;
// Elements distribute their height based on two rules:
// 1) Elements with rowspan/colspan == 1 distribute their height first
// 2) Everything else distributes in a LIFO manner.
// As such, add all UIElements with rowspan/colspan == 1 after the separator in
// the list and everything else before it. Then to process, just keep popping
// elements off the end of the list.
if (!starAuto)
{
node = new GridNode(this.rowMatrix, row + rowspan - 1, row, desired.Height);
separatorIndex = sizes.IndexOf(separator);
sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node);
}
node = new GridNode(this.colMatrix, col + colspan - 1, col, desired.Width);
separatorIndex = sizes.IndexOf(separator);
sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node);
}
sizes.Remove(separator);
while (sizes.Count > 0)
{
node = sizes.Last();
node.Matrix[node.Row, node.Column].DesiredSize = Math.Max(node.Matrix[node.Row, node.Column].DesiredSize, node.Size);
this.AllocateDesiredSize(rowCount, colCount);
sizes.Remove(node);
}
sizes.Add(separator);
}
// Once we have measured and distributed all sizes, we have to store
// the results. Every time we want to expand the rows/cols, this will
// be used as the baseline.
this.SaveMeasureResults();
sizes.Remove(separator);
double gridSizeX = 0;
double gridSizeY = 0;
for (int c = 0; c < colCount; c++)
{
gridSizeX += this.colMatrix[c, c].DesiredSize;
}
for (int r = 0; r < rowCount; r++)
{
gridSizeY += this.rowMatrix[r, r].DesiredSize;
}
return new Size(gridSizeX, gridSizeY);
}
protected override Size ArrangeOverride(Size finalSize)
{
int colCount = this.ColumnDefinitions.Count;
int rowCount = this.RowDefinitions.Count;
int colMatrixDim = this.colMatrix.GetUpperBound(0) + 1;
int rowMatrixDim = this.rowMatrix.GetUpperBound(0) + 1;
this.RestoreMeasureResults();
double totalConsumedX = 0;
double totalConsumedY = 0;
for (int c = 0; c < colMatrixDim; c++)
{
this.colMatrix[c, c].OfferedSize = this.colMatrix[c, c].DesiredSize;
totalConsumedX += this.colMatrix[c, c].OfferedSize;
}
for (int r = 0; r < rowMatrixDim; r++)
{
this.rowMatrix[r, r].OfferedSize = this.rowMatrix[r, r].DesiredSize;
totalConsumedY += this.rowMatrix[r, r].OfferedSize;
}
if (totalConsumedX != finalSize.Width)
{
this.ExpandStarCols(finalSize);
}
if (totalConsumedY != finalSize.Height)
{
this.ExpandStarRows(finalSize);
}
for (int c = 0; c < colCount; c++)
{
this.ColumnDefinitions[c].ActualWidth = this.colMatrix[c, c].OfferedSize;
}
for (int r = 0; r < rowCount; r++)
{
this.RowDefinitions[r].ActualHeight = this.rowMatrix[r, r].OfferedSize;
}
foreach (Control child in this.Children)
{
int col = Math.Min(GetColumn(child), colMatrixDim - 1);
int row = Math.Min(GetRow(child), rowMatrixDim - 1);
int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - col);
int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - row);
double childFinalX = 0;
double childFinalY = 0;
double childFinalW = 0;
double childFinalH = 0;
for (int c = 0; c < col; c++)
{
childFinalX += this.colMatrix[c, c].OfferedSize;
}
for (int c = col; c < col + colspan; c++)
{
childFinalW += this.colMatrix[c, c].OfferedSize;
}
for (int r = 0; r < row; r++)
{
childFinalY += this.rowMatrix[r, r].OfferedSize;
}
for (int r = row; r < row + rowspan; r++)
{
childFinalH += this.rowMatrix[r, r].OfferedSize;
}
child.Arrange(new Rect(childFinalX, childFinalY, childFinalW, childFinalH));
}
return finalSize;
}
private static double Clamp(double val, double min, double max)
{
if (val < min)
{
return min;
}
else if (val > max)
{
return max;
}
else
{
return val;
}
}
private void CreateMatrices(int rowCount, int colCount)
{
if (this.rowMatrix == null || this.colMatrix == null ||
this.rowMatrix.GetUpperBound(0) != rowCount - 1 ||
this.colMatrix.GetUpperBound(0) != colCount - 1)
{
this.rowMatrix = new Segment[rowCount, rowCount];
this.colMatrix = new Segment[colCount, colCount];
}
}
private void ExpandStarCols(Size availableSize)
{
int columnsCount = this.ColumnDefinitions.Count;
double width = availableSize.Width;
for (int i = 0; i < this.colMatrix.GetUpperBound(0) + 1; i++)
{
if (this.colMatrix[i, i].Type == GridUnitType.Star)
{
this.colMatrix[i, i].OfferedSize = 0;
}
else
{
width = Math.Max(availableSize.Width - this.colMatrix[i, i].OfferedSize, 0);
}
}
this.AssignSize(this.colMatrix, 0, this.colMatrix.GetUpperBound(0), ref width, GridUnitType.Star, false);
width = Math.Max(0, width);
if (columnsCount > 0)
{
for (int i = 0; i < this.colMatrix.GetUpperBound(0) + 1; i++)
{
if (this.colMatrix[i, i].Type == GridUnitType.Star)
{
this.ColumnDefinitions[i].ActualWidth = this.colMatrix[i, i].OfferedSize;
}
}
}
}
private void ExpandStarRows(Size availableSize)
{
int rowCount = this.RowDefinitions.Count;
double height = availableSize.Height;
// When expanding star rows, we need to zero out their height before
// calling AssignSize. AssignSize takes care of distributing the
// available size when there are Mins and Maxs applied.
for (int i = 0; i < this.rowMatrix.GetUpperBound(0) + 1; i++)
{
if (this.rowMatrix[i, i].Type == GridUnitType.Star)
{
this.rowMatrix[i, i].OfferedSize = 0.0;
}
else
{
height = Math.Max(availableSize.Height - this.rowMatrix[i, i].OfferedSize, 0);
}
}
this.AssignSize(this.rowMatrix, 0, this.rowMatrix.GetUpperBound(0), ref height, GridUnitType.Star, false);
if (rowCount > 0)
{
for (int i = 0; i < this.rowMatrix.GetUpperBound(0) + 1; i++)
{
if (this.rowMatrix[i, i].Type == GridUnitType.Star)
{
this.RowDefinitions[i].ActualHeight = this.rowMatrix[i, i].OfferedSize;
}
}
}
}
private void AssignSize(
Segment[,] matrix,
int start,
int end,
ref double size,
GridUnitType type,
bool desiredSize)
{
double count = 0;
bool assigned;
// Count how many segments are of the correct type. If we're measuring Star rows/cols
// we need to count the number of stars instead.
for (int i = start; i <= end; i++)
{
double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
if (segmentSize < matrix[i, i].Max)
{
count += type == GridUnitType.Star ? matrix[i, i].Stars : 1;
}
}
do
{
double contribution = size / count;
assigned = false;
for (int i = start; i <= end; i++)
{
double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
if (!(matrix[i, i].Type == type && segmentSize < matrix[i, i].Max))
{
continue;
}
double newsize = segmentSize;
newsize += contribution * (type == GridUnitType.Star ? matrix[i, i].Stars : 1);
newsize = Math.Min(newsize, matrix[i, i].Max);
assigned |= newsize > segmentSize;
size -= newsize - segmentSize;
if (desiredSize)
{
matrix[i, i].DesiredSize = newsize;
}
else
{
matrix[i, i].OfferedSize = newsize;
}
}
}
while (assigned);
}
private void AllocateDesiredSize(int rowCount, int colCount)
{
// First allocate the heights of the RowDefinitions, then allocate
// the widths of the ColumnDefinitions.
for (int i = 0; i < 2; i++)
{
Segment[,] matrix = i == 0 ? this.rowMatrix : this.colMatrix;
int count = i == 0 ? rowCount : colCount;
for (int row = count - 1; row >= 0; row--)
{
for (int col = row; col >= 0; col--)
{
bool spansStar = false;
for (int j = row; j >= col; j--)
{
spansStar |= matrix[j, j].Type == GridUnitType.Star;
}
// This is the amount of pixels which must be available between the grid rows
// at index 'col' and 'row'. i.e. if 'row' == 0 and 'col' == 2, there must
// be at least 'matrix [row][col].size' pixels of height allocated between
// all the rows in the range col -> row.
double current = matrix[row, col].DesiredSize;
// Count how many pixels have already been allocated between the grid rows
// in the range col -> row. The amount of pixels allocated to each grid row/column
// is found on the diagonal of the matrix.
double totalAllocated = 0;
for (int k = row; k >= col; k--)
{
totalAllocated += matrix[k, k].DesiredSize;
}
// If the size requirement has not been met, allocate the additional required
// size between 'pixel' rows, then 'star' rows, finally 'auto' rows, until all
// height has been assigned.
if (totalAllocated < current)
{
double additional = current - totalAllocated;
if (spansStar)
{
this.AssignSize(matrix, col, row, ref additional, GridUnitType.Star, true);
}
else
{
this.AssignSize(matrix, col, row, ref additional, GridUnitType.Pixel, true);
this.AssignSize(matrix, col, row, ref additional, GridUnitType.Auto, true);
}
}
}
}
}
for (int r = 0; r < this.rowMatrix.GetUpperBound(0) + 1; r++)
{
this.rowMatrix[r, r].OfferedSize = this.rowMatrix[r, r].DesiredSize;
}
for (int c = 0; c < this.colMatrix.GetUpperBound(0) + 1; c++)
{
this.colMatrix[c, c].OfferedSize = this.colMatrix[c, c].DesiredSize;
}
}
private void SaveMeasureResults()
{
for (int i = 0; i < this.rowMatrix.GetUpperBound(0) + 1; i++)
{
for (int j = 0; j < this.rowMatrix.GetUpperBound(0) + 1; j++)
{
this.rowMatrix[i, j].OriginalSize = this.rowMatrix[i, j].OfferedSize;
}
}
for (int i = 0; i < this.colMatrix.GetUpperBound(0); i++)
{
for (int j = 0; j < this.colMatrix.GetUpperBound(0); j++)
{
this.colMatrix[i, j].OriginalSize = this.colMatrix[i, j].OfferedSize;
}
}
}
private void RestoreMeasureResults()
{
for (int i = 0; i < this.rowMatrix.GetUpperBound(0) + 1; i++)
{
for (int j = 0; j < this.rowMatrix.GetUpperBound(0) + 1; j++)
{
this.rowMatrix[i, j].OfferedSize = this.rowMatrix[i, j].OriginalSize;
}
}
for (int i = 0; i < this.colMatrix.GetUpperBound(0) + 1; i++)
{
for (int j = 0; j < this.colMatrix.GetUpperBound(0) + 1; j++)
{
this.colMatrix[i, j].OfferedSize = this.colMatrix[i, j].OriginalSize;
}
}
}
private struct Segment
{
public double OriginalSize;
public double Max;
public double Min;
public double DesiredSize;
public double OfferedSize;
public double Stars;
public GridUnitType Type;
public Segment(double offeredSize, double min, double max, GridUnitType type)
{
this.OriginalSize = 0;
this.Min = min;
this.Max = max;
this.DesiredSize = 0;
this.OfferedSize = offeredSize;
this.Stars = 0;
this.Type = type;
}
public void Init(double offeredSize, double min, double max, GridUnitType type)
{
this.OfferedSize = offeredSize;
this.Min = min;
this.Max = max;
this.Type = type;
}
}
private struct GridNode
{
public int Row;
public int Column;
public double Size;
public Segment[,] Matrix;
public GridNode(Segment[,] matrix, int row, int col, double size)
{
this.Matrix = matrix;
this.Row = row;
this.Column = col;
this.Size = size;
}
}
private class GridWalker
{
public GridWalker(Grid grid, Segment[,] rowMatrix, Segment[,] colMatrix)
{
foreach (Control child in grid.Children)
{
bool starCol = false;
bool starRow = false;
bool autoCol = false;
bool autoRow = false;
int col = Math.Min(Grid.GetColumn(child), colMatrix.GetUpperBound(0));
int row = Math.Min(Grid.GetRow(child), rowMatrix.GetUpperBound(0));
int colspan = Math.Min(Grid.GetColumnSpan(child), colMatrix.GetUpperBound(0));
int rowspan = Math.Min(Grid.GetRowSpan(child), rowMatrix.GetUpperBound(0));
for (int r = row; r < row + rowspan; r++)
{
starRow |= rowMatrix[r, r].Type == GridUnitType.Star;
autoRow |= rowMatrix[r, r].Type == GridUnitType.Auto;
}
for (int c = col; c < col + colspan; c++)
{
starCol |= colMatrix[c, c].Type == GridUnitType.Star;
autoCol |= colMatrix[c, c].Type == GridUnitType.Auto;
}
this.HasAutoAuto |= autoRow && autoCol && !starRow && !starCol;
this.HasStarAuto |= starRow && autoCol;
this.HasAutoStar |= autoRow && starCol;
}
}
public bool HasAutoAuto { get; private set; }
public bool HasStarAuto { get; private set; }
public bool HasAutoStar { get; private set; }
}
}
}

121
Perspex.Controls/GridLength.cs

@ -0,0 +1,121 @@
// -----------------------------------------------------------------------
// <copyright file="GridLength.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
public enum GridUnitType
{
Auto = 0,
Pixel = 1,
Star = 2,
}
public struct GridLength : IEquatable<GridLength>
{
private GridUnitType type;
private double value;
public GridLength(double value)
: this(value, GridUnitType.Pixel)
{
}
public GridLength(double value, GridUnitType type)
{
if (value < 0 || double.IsNaN(value) || double.IsInfinity(value))
{
throw new ArgumentException("Invalid value", "value");
}
if (type < GridUnitType.Auto || type > GridUnitType.Star)
{
throw new ArgumentException("Invalid value", "type");
}
this.type = type;
this.value = value;
}
public static GridLength Auto
{
get { return new GridLength(0, GridUnitType.Auto); }
}
public GridUnitType GridUnitType
{
get { return this.type; }
}
public bool IsAbsolute
{
get { return this.type == GridUnitType.Pixel; }
}
public bool IsAuto
{
get { return this.type == GridUnitType.Auto; }
}
public bool IsStar
{
get { return this.type == GridUnitType.Star; }
}
public double Value
{
get { return this.value; }
}
public static bool operator ==(GridLength a, GridLength b)
{
return (a.IsAuto && b.IsAuto) || (a.value == b.value && a.type == b.type);
}
public static bool operator !=(GridLength gl1, GridLength gl2)
{
return !(gl1 == gl2);
}
public override bool Equals(object o)
{
if (o == null)
{
return false;
}
if (!(o is GridLength))
{
return false;
}
return this == (GridLength)o;
}
public bool Equals(GridLength gridLength)
{
return this == gridLength;
}
public override int GetHashCode()
{
return this.value.GetHashCode() ^ this.type.GetHashCode();
}
public override string ToString()
{
if (this.IsAuto)
{
return "Auto";
}
string s = this.value.ToString();
return this.IsStar ? s + "*" : s;
}
}
}

24
Perspex.Controls/HeaderedContentControl.cs

@ -0,0 +1,24 @@
// -----------------------------------------------------------------------
// <copyright file="ContentControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
public class HeaderedContentControl : ContentControl, IHeadered
{
public static readonly PerspexProperty<object> HeaderProperty =
PerspexProperty.Register<ContentControl, object>("Header");
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
}
}

25
Perspex.Controls/HeaderedItemsControl.cs

@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="HeaderedItemsControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections;
using System.Linq;
using System.Reactive.Linq;
public class HeaderedItemsControl : ItemsControl
{
public static readonly PerspexProperty<object> HeaderProperty =
HeaderedContentControl.HeaderProperty.AddOwner<HeaderedItemsControl>();
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
}
}

14
Perspex.Controls/IGlobalDataTemplates.cs

@ -0,0 +1,14 @@
// -----------------------------------------------------------------------
// <copyright file="IGlobalDataTemplates.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System.Collections.Generic;
public interface IGlobalDataTemplates : IEnumerable<DataTemplate>
{
}
}

13
Perspex.Controls/IHeadered.cs

@ -0,0 +1,13 @@
// -----------------------------------------------------------------------
// <copyright file="IHeadered.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public interface IHeadered
{
object Header { get; set; }
}
}

115
Perspex.Controls/Image.cs

@ -0,0 +1,115 @@
// -----------------------------------------------------------------------
// <copyright file="Image.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using Perspex.Media;
using Perspex.Media.Imaging;
public class Image : Control
{
public static readonly PerspexProperty<Bitmap> SourceProperty =
PerspexProperty.Register<Image, Bitmap>("Source");
public static readonly PerspexProperty<Stretch> StretchProperty =
PerspexProperty.Register<Image, Stretch>("Stretch", Stretch.Uniform);
public Bitmap Source
{
get { return this.GetValue(SourceProperty); }
set { this.SetValue(SourceProperty, value); }
}
public Stretch Stretch
{
get { return (Stretch)this.GetValue(StretchProperty); }
set { this.SetValue(StretchProperty, value); }
}
public override void Render(IDrawingContext drawingContext)
{
Brush background = this.Background;
Bitmap source = this.Source;
if (background != null)
{
drawingContext.FillRectange(background, new Rect(this.ActualSize));
}
if (source != null)
{
Rect viewPort = new Rect(this.ActualSize);
Size sourceSize = new Size(source.PixelWidth, source.PixelHeight);
Vector scale = CalculateScaling(this.ActualSize, sourceSize, this.Stretch);
Size scaledSize = sourceSize * scale;
Rect destRect = viewPort
.Center(new Rect(scaledSize))
.Intersect(viewPort);
Rect sourceRect = new Rect(sourceSize)
.Center(new Rect(destRect.Size / scale));
drawingContext.DrawImage(source, 1, sourceRect, destRect);
}
}
private static Vector CalculateScaling(Size availableSize, Size imageSize, Stretch stretch)
{
double scaleX = 1;
double scaleY = 1;
if (stretch != Stretch.None)
{
scaleX = availableSize.Width / imageSize.Width;
scaleY = availableSize.Height / imageSize.Height;
switch (stretch)
{
case Stretch.Uniform:
scaleX = scaleY = Math.Min(scaleX, scaleY);
break;
case Stretch.UniformToFill:
scaleX = scaleY = Math.Max(scaleX, scaleY);
break;
}
}
return new Vector(scaleX, scaleY);
}
protected override Size MeasureOverride(Size availableSize)
{
double width = 0;
double height = 0;
Vector scale = new Vector();
if (this.Source != null)
{
width = this.Source.PixelWidth;
height = this.Source.PixelHeight;
if (this.Width > 0)
{
availableSize = new Size(this.Width, availableSize.Height);
}
if (this.Height > 0)
{
availableSize = new Size(availableSize.Width, this.Height);
}
scale = CalculateScaling(availableSize, new Size(width, height), this.Stretch);
}
return new Size(width * scale.X, height * scale.Y);
}
protected override Size ArrangeOverride(Size finalSize)
{
return finalSize;
}
}
}

137
Perspex.Controls/ItemsControl.cs

@ -0,0 +1,137 @@
// -----------------------------------------------------------------------
// <copyright file="ItemsControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
public class ItemsControl : TemplatedControl
{
private static readonly ItemsPanelTemplate DefaultPanel =
new ItemsPanelTemplate(() => new StackPanel { Orientation = Orientation.Vertical });
public static readonly PerspexProperty<IEnumerable> ItemsProperty =
PerspexProperty.Register<ItemsControl, IEnumerable>("Items");
public static readonly PerspexProperty<ItemsPanelTemplate> ItemsPanelProperty =
PerspexProperty.Register<ItemsControl, ItemsPanelTemplate>("ItemsPanel", defaultValue: DefaultPanel);
private Dictionary<object, Control> controlsByItem = new Dictionary<object, Control>();
private Dictionary<Control, object> itemsByControl = new Dictionary<Control, object>();
public ItemsControl()
{
this.GetObservableWithHistory(ItemsProperty).Subscribe(this.ItemsChanged);
}
public IEnumerable Items
{
get { return this.GetValue(ItemsProperty); }
set { this.SetValue(ItemsProperty, value); }
}
public ItemsPanelTemplate ItemsPanel
{
get { return this.GetValue(ItemsPanelProperty); }
set { this.SetValue(ItemsPanelProperty, value); }
}
public Control GetControlForItem(object item)
{
Control result;
this.controlsByItem.TryGetValue(item, out result);
return result;
}
public object GetItemForControl(Control control)
{
object result;
this.itemsByControl.TryGetValue(control, out result);
return result;
}
public IEnumerable<Control> GetAllItemControls()
{
return this.controlsByItem.Values;
}
internal Control CreateItemControl(object item)
{
Control control = this.CreateItemControlOverride(item);
this.itemsByControl.Add(control, item);
this.controlsByItem.Add(item, control);
return control;
}
internal void RemoveItemControls(IEnumerable items)
{
foreach (object i in items)
{
Control control = this.GetControlForItem(i);
this.controlsByItem.Remove(i);
this.itemsByControl.Remove(control);
}
}
internal void ClearItemControls()
{
this.controlsByItem.Clear();
this.itemsByControl.Clear();
}
protected virtual Control CreateItemControlOverride(object item)
{
return (item as Control) ?? this.GetDataTemplate(item).Build(item);
}
private void ItemsChanged(Tuple<IEnumerable, IEnumerable> value)
{
INotifyPropertyChanged inpc = value.Item1 as INotifyPropertyChanged;
if (inpc != null)
{
inpc.PropertyChanged -= ItemsPropertyChanged;
}
if (value.Item2 == null || !value.Item2.OfType<object>().Any())
{
this.Classes.Add(":empty");
}
else
{
this.Classes.Remove(":empty");
}
inpc = value.Item2 as INotifyPropertyChanged;
if (inpc != null)
{
inpc.PropertyChanged += ItemsPropertyChanged;
}
}
private void ItemsPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Count")
{
if (((IList)sender).Count == 0)
{
this.Classes.Add(":empty");
}
else
{
this.Classes.Remove(":empty");
}
}
}
}
}

20
Perspex.Controls/ItemsPanelTemplate.cs

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Perspex.Controls
{
public class ItemsPanelTemplate
{
public ItemsPanelTemplate(Func<Panel> build)
{
Contract.Requires<ArgumentNullException>(build != null);
this.Build = build;
}
public Func<Panel> Build { get; private set; }
}
}

178
Perspex.Controls/ItemsPresenter.cs

@ -0,0 +1,178 @@
// -----------------------------------------------------------------------
// <copyright file="ItemsControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
public class ItemsPresenter : Control, IVisual
{
public static readonly PerspexProperty<IEnumerable> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<ItemsPresenter>();
public static readonly PerspexProperty<ItemsPanelTemplate> ItemsPanelProperty =
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenter>();
private Panel panel;
public ItemsPresenter()
{
this.GetObservableWithHistory(ItemsProperty).Subscribe(this.ItemsChanged);
}
public IEnumerable Items
{
get { return this.GetValue(ItemsProperty); }
set { this.SetValue(ItemsProperty, value); }
}
public ItemsPanelTemplate ItemsPanel
{
get { return this.GetValue(ItemsPanelProperty); }
set { this.SetValue(ItemsPanelProperty, value); }
}
protected override Size MeasureOverride(Size availableSize)
{
Panel panel = this.GetPanel();
panel.Measure(availableSize);
return panel.DesiredSize.Value;
}
protected override Size ArrangeOverride(Size finalSize)
{
this.GetPanel().Arrange(new Rect(finalSize));
return finalSize;
}
private Control CreateItemControl(object item)
{
ItemsControl i = this.TemplatedParent as ItemsControl;
if (i != null)
{
return i.CreateItemControl(item);
}
else
{
return this.GetDataTemplate(item).Build(item) as Control;
}
}
private IEnumerable<Control> CreateItemControls(IEnumerable items)
{
if (items != null)
{
return items
.Cast<object>()
.Select(x => this.CreateItemControl(x))
.OfType<Control>();
}
else
{
return Enumerable.Empty<Control>();
}
}
private void ClearItemControls()
{
ItemsControl i = this.TemplatedParent as ItemsControl;
if (i != null)
{
i.ClearItemControls();
}
}
private void RemoveItemControls(IEnumerable items)
{
ItemsControl i = this.TemplatedParent as ItemsControl;
if (i != null)
{
i.RemoveItemControls(items);
}
}
private Panel GetPanel()
{
if (this.panel == null && this.ItemsPanel != null)
{
this.panel = this.ItemsPanel.Build();
this.ItemsChanged(Tuple.Create(default(IEnumerable), this.Items));
}
return this.panel;
}
private void ItemsChanged(Tuple<IEnumerable, IEnumerable> value)
{
if (value.Item1 != null)
{
INotifyCollectionChanged incc = value.Item1 as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged -= this.ItemsCollectionChanged;
}
}
this.ClearItemControls();
if (this.panel != null)
{
var controls = this.CreateItemControls(value.Item2).ToList();
foreach (var control in controls)
{
control.TemplatedParent = null;
}
this.panel.Children = new Controls(controls);
INotifyCollectionChanged incc = value.Item2 as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += this.ItemsCollectionChanged;
}
}
}
private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.panel != null)
{
// TODO: Handle Move and Replace.
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
var controls = this.CreateItemControls(e.NewItems).ToList();
foreach (var control in controls)
{
control.TemplatedParent = null;
}
this.panel.Children.AddRange(controls);
break;
case NotifyCollectionChangedAction.Remove:
this.RemoveItemControls(e.OldItems);
break;
case NotifyCollectionChangedAction.Reset:
this.ItemsChanged(Tuple.Create(this.Items, this.Items));
break;
}
}
}
}
}

45
Perspex.Controls/Panel.cs

@ -0,0 +1,45 @@
// -----------------------------------------------------------------------
// <copyright file="Panel.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Base class for controls that can contain multiple children.
/// </summary>
public class Panel : Control
{
private Controls children;
public Controls Children
{
get
{
if (this.children == null)
{
this.children = new Controls();
}
return this.children;
}
set
{
Contract.Requires<ArgumentNullException>(value != null);
if (this.children != value)
{
this.children = value;
this.ClearVisualChildren();
this.AddVisualChildren(value);
}
}
}
}
}

138
Perspex.Controls/Perspex.Controls.csproj

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D2221C82-4A25-4583-9B43-D791E3F6820C}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex.Controls</RootNamespace>
<AssemblyName>Perspex.Controls</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="Border.cs" />
<Compile Include="Button.cs" />
<Compile Include="CheckBox.cs" />
<Compile Include="ColumnDefinition.cs" />
<Compile Include="ColumnDefinitions.cs" />
<Compile Include="ContentControl.cs" />
<Compile Include="ContentPresenter.cs" />
<Compile Include="Control.cs" />
<Compile Include="ControlExtensions.cs" />
<Compile Include="Controls.cs" />
<Compile Include="ControlTemplate.cs" />
<Compile Include="DataTemplate.cs" />
<Compile Include="IGlobalDataTemplates.cs" />
<Compile Include="DataTemplates.cs" />
<Compile Include="Decorator.cs" />
<Compile Include="DefinitionBase.cs" />
<Compile Include="Grid.cs" />
<Compile Include="GridLength.cs" />
<Compile Include="HeaderedContentControl.cs" />
<Compile Include="HeaderedItemsControl.cs" />
<Compile Include="IHeadered.cs" />
<Compile Include="Image.cs" />
<Compile Include="ItemsControl.cs" />
<Compile Include="ItemsPanelTemplate.cs" />
<Compile Include="ItemsPresenter.cs" />
<Compile Include="Panel.cs" />
<Compile Include="RowDefinition.cs" />
<Compile Include="RowDefinitions.cs" />
<Compile Include="SelectingItemsControl.cs" />
<Compile Include="StackPanel.cs" />
<Compile Include="TabControl.cs" />
<Compile Include="TabItem.cs" />
<Compile Include="TabStrip.cs" />
<Compile Include="TemplatedControl.cs" />
<Compile Include="TextBlock.cs" />
<Compile Include="TextBox.cs" />
<Compile Include="TextBoxView.cs" />
<Compile Include="ToggleButton.cs" />
<Compile Include="TreeDataTemplate.cs" />
<Compile Include="TreeView.cs" />
<Compile Include="TreeViewItem.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Splat">
<HintPath>..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Core">
<HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Interfaces">
<HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Linq">
<HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Input\Perspex.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Perspex.Input</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets" Condition="Exists('..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

30
Perspex.Controls/Properties/AssemblyInfo.cs

@ -0,0 +1,30 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Perspex.Controls")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Perspex.Controls")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

53
Perspex.Controls/RowDefinition.cs

@ -0,0 +1,53 @@
// -----------------------------------------------------------------------
// <copyright file="RowDefinition.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public class RowDefinition : DefinitionBase
{
public static readonly PerspexProperty<double> MaxHeightProperty =
PerspexProperty.Register<RowDefinition, double>("MaxHeight", double.PositiveInfinity);
public static readonly PerspexProperty<double> MinHeightProperty =
PerspexProperty.Register<RowDefinition, double>("MinHeight");
public static readonly PerspexProperty<GridLength> HeightProperty =
PerspexProperty.Register<RowDefinition, GridLength>("Height", new GridLength(1, GridUnitType.Star));
public RowDefinition()
{
}
public RowDefinition(GridLength height)
{
this.Height = height;
}
public double ActualHeight
{
get;
internal set;
}
public double MaxHeight
{
get { return this.GetValue(MaxHeightProperty); }
set { this.SetValue(MaxHeightProperty, value); }
}
public double MinHeight
{
get { return this.GetValue(MinHeightProperty); }
set { this.SetValue(MinHeightProperty, value); }
}
public GridLength Height
{
get { return this.GetValue(HeightProperty); }
set { this.SetValue(HeightProperty, value); }
}
}
}

12
Perspex.Controls/RowDefinitions.cs

@ -0,0 +1,12 @@
// -----------------------------------------------------------------------
// <copyright file="RowDefinitions.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public class RowDefinitions : PerspexList<RowDefinition>
{
}
}

20
Perspex.Controls/SelectingItemsControl.cs

@ -0,0 +1,20 @@
// -----------------------------------------------------------------------
// <copyright file="SelectingItemsControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public class SelectingItemsControl : ItemsControl
{
public static readonly PerspexProperty<object> SelectedItemProperty =
PerspexProperty.Register<SelectingItemsControl, object>("SelectedItem");
public object SelectedItem
{
get { return this.GetValue(SelectedItemProperty); }
set { this.SetValue(SelectedItemProperty, value); }
}
}
}

155
Perspex.Controls/StackPanel.cs

@ -0,0 +1,155 @@
// -----------------------------------------------------------------------
// <copyright file="StackPanel.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public enum Orientation
{
Vertical,
Horizontal,
}
public class StackPanel : Panel
{
public static readonly PerspexProperty<double> GapProperty =
PerspexProperty.Register<StackPanel, double>("Gap");
public static readonly PerspexProperty<Orientation> OrientationProperty =
PerspexProperty.Register<StackPanel, Orientation>("Orientation");
public double Gap
{
get { return this.GetValue(GapProperty); }
set { this.SetValue(GapProperty, value); }
}
public Orientation Orientation
{
get { return this.GetValue(OrientationProperty); }
set { this.SetValue(OrientationProperty, value); }
}
protected override Size MeasureOverride(Size availableSize)
{
double childAvailableWidth = double.PositiveInfinity;
double childAvailableHeight = double.PositiveInfinity;
if (this.Orientation == Orientation.Vertical)
{
childAvailableWidth = availableSize.Width;
if (!double.IsNaN(this.Width))
{
childAvailableWidth = this.Width;
}
childAvailableWidth = Math.Min(childAvailableWidth, this.MaxWidth);
childAvailableWidth = Math.Max(childAvailableWidth, this.MinWidth);
}
else
{
childAvailableHeight = availableSize.Height;
if (!double.IsNaN(this.Height))
{
childAvailableHeight = this.Height;
}
childAvailableHeight = Math.Min(childAvailableHeight, this.MaxHeight);
childAvailableHeight = Math.Max(childAvailableHeight, this.MinHeight);
}
double measuredWidth = 0;
double measuredHeight = 0;
double gap = this.Gap;
foreach (Control child in this.Children)
{
child.Measure(new Size(childAvailableWidth, childAvailableHeight));
Size size = child.DesiredSize.Value;
if (Orientation == Orientation.Vertical)
{
measuredHeight += size.Height + gap;
measuredWidth = Math.Max(measuredWidth, size.Width);
}
else
{
measuredWidth += size.Width + gap;
measuredHeight = Math.Max(measuredHeight, size.Height);
}
}
return new Size(measuredWidth, measuredHeight);
}
protected override Size ArrangeOverride(Size finalSize)
{
double arrangedWidth = finalSize.Width;
double arrangedHeight = finalSize.Height;
double gap = this.Gap;
if (Orientation == Orientation.Vertical)
{
arrangedHeight = 0;
}
else
{
arrangedWidth = 0;
}
foreach (Control child in this.Children.Where(x => x.DesiredSize.HasValue))
{
double childWidth = child.DesiredSize.Value.Width;
double childHeight = child.DesiredSize.Value.Height;
if (Orientation == Orientation.Vertical)
{
childWidth = finalSize.Width;
Rect childFinal = new Rect(0, arrangedHeight, childWidth, childHeight);
if (childFinal.IsEmpty)
{
child.Arrange(new Rect());
}
else
{
child.Arrange(childFinal);
}
arrangedWidth = Math.Max(arrangedWidth, childWidth);
arrangedHeight += childHeight + gap;
}
else
{
childHeight = finalSize.Height;
Rect childFinal = new Rect(arrangedWidth, 0, childWidth, childHeight);
child.Arrange(childFinal);
arrangedWidth += childWidth + gap;
arrangedHeight = Math.Max(arrangedHeight, childHeight);
}
}
if (Orientation == Orientation.Vertical)
{
arrangedHeight = Math.Max(arrangedHeight - gap, finalSize.Height);
}
else
{
arrangedWidth = Math.Max(arrangedWidth - gap, finalSize.Width);
}
return new Size(arrangedWidth, arrangedHeight);
}
}
}

62
Perspex.Controls/TabControl.cs

@ -0,0 +1,62 @@
// -----------------------------------------------------------------------
// <copyright file="TabControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
public class TabControl : SelectingItemsControl
{
public static readonly PerspexProperty<object> SelectedContentProperty =
PerspexProperty.Register<TabControl, object>("SelectedContent");
private TabStrip tabStrip;
public TabControl()
{
this.GetObservable(SelectedItemProperty).Skip(1).Subscribe(this.SelectedItemChanged);
}
protected override void OnTemplateApplied()
{
this.tabStrip = this.GetTemplateControls()
.OfType<TabStrip>()
.FirstOrDefault();
if (this.tabStrip != null)
{
if (this.IsSet(SelectedItemProperty))
{
this.SelectedItem = SelectedItem;
}
this.tabStrip.GetObservable(TabStrip.SelectedItemProperty).Subscribe(x =>
{
this.SelectedItem = x;
});
}
}
private void SelectedItemChanged(object item)
{
this.SelectedItem = item;
ContentControl content = item as ContentControl;
if (content != null)
{
this.SetValue(SelectedContentProperty, content.Content);
}
else
{
this.SetValue(SelectedContentProperty, item);
}
}
}
}

26
Perspex.Controls/TabItem.cs

@ -0,0 +1,26 @@
// -----------------------------------------------------------------------
// <copyright file="TabItem.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public class TabItem : HeaderedContentControl
{
public static readonly PerspexProperty<bool> IsSelectedProperty =
PerspexProperty.Register<TabItem, bool>("IsSelected");
public TabItem()
{
this.AddPseudoClass(IsSelectedProperty, ":selected");
AffectsRender(IsSelectedProperty);
}
public bool IsSelected
{
get { return this.GetValue(IsSelectedProperty); }
set { this.SetValue(IsSelectedProperty, value); }
}
}
}

88
Perspex.Controls/TabStrip.cs

@ -0,0 +1,88 @@
// -----------------------------------------------------------------------
// <copyright file="TabStrip.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections;
using System.Linq;
using Perspex.Input;
public class TabStrip : SelectingItemsControl
{
private static readonly ItemsPanelTemplate PanelTemplate = new ItemsPanelTemplate(
() => new StackPanel());
static TabStrip()
{
ItemsPanelProperty.OverrideDefaultValue(typeof(TabStrip), PanelTemplate);
}
public TabStrip()
{
this.PointerPressed += this.OnPointerPressed;
this.GetObservable(ItemsProperty).Subscribe(this.ItemsChanged);
this.GetObservable(SelectedItemProperty).Subscribe(this.SelectedItemChanged);
}
protected override Control CreateItemControlOverride(object item)
{
TabItem result = item as TabItem;
if (result == null)
{
result = new TabItem
{
Content = this.GetDataTemplate(item).Build(item),
};
}
result.IsSelected = this.SelectedItem == item;
return result;
}
private void ItemsChanged(IEnumerable items)
{
if (items != null)
{
this.SelectedItem =
items.OfType<TabItem>().FirstOrDefault(x => x.IsSelected) ??
items.OfType<object>().FirstOrDefault();
}
else
{
this.SelectedItem = null;
}
}
private void OnPointerPressed(object sender, PointerEventArgs e)
{
IVisual source = (IVisual)e.Source;
ContentPresenter presenter = source.GetVisualAncestors()
.OfType<ContentPresenter>()
.FirstOrDefault();
if (presenter != null)
{
TabItem item = presenter.TemplatedParent as TabItem;
if (item != null)
{
this.SelectedItem = item;
}
}
}
private void SelectedItemChanged(object selectedItem)
{
foreach (TabItem item in this.GetAllItemControls())
{
item.IsSelected = item == selectedItem;
}
}
}
}

74
Perspex.Controls/TemplatedControl.cs

@ -0,0 +1,74 @@
// -----------------------------------------------------------------------
// <copyright file="TemplatedControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Media;
using Perspex.Styling;
using Splat;
public class TemplatedControl : Control, IVisual, ITemplatedControl
{
public static readonly PerspexProperty<ControlTemplate> TemplateProperty =
PerspexProperty.Register<TemplatedControl, ControlTemplate>("Template");
public ControlTemplate Template
{
get { return this.GetValue(TemplateProperty); }
set { this.SetValue(TemplateProperty, value); }
}
public sealed override void Render(IDrawingContext context)
{
}
protected override Size ArrangeOverride(Size finalSize)
{
Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control;
if (child != null)
{
child.Arrange(new Rect(finalSize));
return child.ActualSize;
}
else
{
return new Size();
}
}
protected override Size MeasureOverride(Size availableSize)
{
Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control;
if (child != null)
{
child.Measure(availableSize);
return child.DesiredSize.Value;
}
return new Size();
}
protected override IEnumerable<Visual> CreateVisualChildren()
{
this.Log().Debug(string.Format(
"Creating template for {0} (#{1:x8})",
this.GetType().Name,
this.GetHashCode()));
var child = this.Template.Build(this);
return Enumerable.Repeat(child, 1);
}
protected virtual void OnTemplateApplied()
{
}
}
}

66
Perspex.Controls/TextBlock.cs

@ -0,0 +1,66 @@
// -----------------------------------------------------------------------
// <copyright file="TextBlock.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using Perspex.Media;
using Perspex.Platform;
using Splat;
public class TextBlock : Control
{
public static readonly PerspexProperty<string> TextProperty =
PerspexProperty.Register<TextBlock, string>("Text");
static TextBlock()
{
AffectsMeasure(TextProperty);
}
public string Text
{
get { return this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); }
}
private FormattedText FormattedText
{
get
{
return new FormattedText
{
FontFamilyName = "Segoe UI",
FontSize = this.FontSize,
Text = this.Text,
};
}
}
public override void Render(IDrawingContext context)
{
Brush background = this.Background;
if (background != null)
{
context.FillRectange(background, new Rect(this.ActualSize));
}
context.DrawText(this.Foreground, new Rect(this.ActualSize), this.FormattedText);
}
protected override Size MeasureOverride(Size availableSize)
{
if (!string.IsNullOrEmpty(this.Text))
{
ITextService textService = Locator.Current.GetService<ITextService>();
return textService.Measure(this.FormattedText, availableSize);
}
return new Size();
}
}
}

133
Perspex.Controls/TextBox.cs

@ -0,0 +1,133 @@
// -----------------------------------------------------------------------
// <copyright file="TextBox.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Linq;
using Perspex.Input;
using Perspex.Platform;
using Perspex.Styling;
using Splat;
public class TextBox : TemplatedControl
{
public static readonly PerspexProperty<string> TextProperty =
TextBlock.TextProperty.AddOwner<TextBox>();
private int caretIndex;
private TextBoxView textBoxView;
static TextBox()
{
FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
}
public TextBox()
{
this.GotFocus += (s, e) => this.textBoxView.GotFocus();
this.LostFocus += (s, e) => this.textBoxView.LostFocus();
this.KeyDown += this.OnKeyDown;
this.PointerPressed += this.OnPointerPressed;
}
public int CaretIndex
{
get
{
return this.caretIndex;
}
set
{
value = Math.Min(Math.Max(value, 0), this.Text.Length);
if (this.caretIndex != value)
{
this.caretIndex = value;
this.textBoxView.CaretMoved();
}
}
}
public string Text
{
get { return this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); }
}
protected override void OnTemplateApplied()
{
Decorator textContainer = this.GetVisualDescendents()
.OfType<Decorator>()
.FirstOrDefault(x => x.Id == "textContainer");
if (textContainer == null)
{
throw new Exception(
"TextBox template doesn't contain a textContainer " +
"or textContainer is not a Decorator.");
}
textContainer.Content = this.textBoxView = new TextBoxView(this);
this.GetObservable(TextProperty).Subscribe(_ => this.textBoxView.InvalidateText());
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
string text = this.Text;
switch (e.Key)
{
case Key.Left:
--this.CaretIndex;
break;
case Key.Right:
++this.CaretIndex;
break;
case Key.Back:
if (this.caretIndex > 0)
{
this.Text = text.Substring(0, this.caretIndex - 1) + text.Substring(this.caretIndex);
--this.CaretIndex;
}
break;
case Key.Delete:
if (this.caretIndex < text.Length)
{
this.Text = text.Substring(0, this.caretIndex) + text.Substring(this.caretIndex + 1);
}
break;
default:
if (!string.IsNullOrEmpty(e.Text))
{
this.Text = text.Substring(0, this.caretIndex) + e.Text + text.Substring(this.caretIndex);
++this.CaretIndex;
}
break;
}
e.Handled = true;
}
private void OnPointerPressed(object sender, PointerEventArgs e)
{
IPlatformRenderInterface platform = Locator.Current.GetService<IPlatformRenderInterface>();
this.CaretIndex = platform.TextService.GetCaretIndex(
this.textBoxView.FormattedText,
e.GetPosition(this.textBoxView),
this.ActualSize);
}
}
}

125
Perspex.Controls/TextBoxView.cs

@ -0,0 +1,125 @@
// -----------------------------------------------------------------------
// <copyright file="TextBoxView.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using Perspex.Media;
using Perspex.Platform;
using Perspex.Threading;
using Splat;
internal class TextBoxView : Control
{
private TextBox parent;
private FormattedText formattedText;
private DispatcherTimer caretTimer;
private bool caretBlink;
public TextBoxView(TextBox parent)
{
this.caretTimer = new DispatcherTimer();
this.caretTimer.Interval = TimeSpan.FromMilliseconds(500);
this.caretTimer.Tick += this.CaretTimerTick;
this.parent = parent;
}
public FormattedText FormattedText
{
get
{
if (this.formattedText == null)
{
this.formattedText = this.CreateFormattedText();
}
return this.formattedText;
}
}
public new void GotFocus()
{
this.caretBlink = true;
this.caretTimer.Start();
}
public new void LostFocus()
{
this.caretTimer.Stop();
this.InvalidateVisual();
}
public void InvalidateText()
{
this.formattedText = null;
this.InvalidateMeasure();
}
internal void CaretMoved()
{
this.caretBlink = true;
this.caretTimer.Stop();
this.caretTimer.Start();
this.InvalidateVisual();
}
public override void Render(IDrawingContext context)
{
Rect rect = new Rect(this.ActualSize);
context.DrawText(Brushes.Black, rect, this.FormattedText);
if (this.parent.IsFocused)
{
ITextService textService = Locator.Current.GetService<ITextService>();
Point caretPos = textService.GetCaretPosition(
this.formattedText,
this.parent.CaretIndex,
this.ActualSize);
double[] lineHeights = textService.GetLineHeights(this.formattedText, this.ActualSize);
Brush caretBrush = Brushes.Black;
if (this.caretBlink)
{
context.DrawLine(
new Pen(caretBrush, 1),
caretPos,
new Point(caretPos.X, caretPos.Y + lineHeights[0]));
}
}
}
protected override Size MeasureOverride(Size constraint)
{
if (!string.IsNullOrEmpty(this.parent.Text))
{
ITextService textService = Locator.Current.GetService<ITextService>();
return textService.Measure(this.FormattedText, constraint);
}
return new Size();
}
private FormattedText CreateFormattedText()
{
return new FormattedText
{
FontFamilyName = "Segoe UI",
FontSize = this.FontSize,
Text = this.parent.Text,
};
}
private void CaretTimerTick(object sender, EventArgs e)
{
this.caretBlink = !this.caretBlink;
this.InvalidateVisual();
}
}
}

28
Perspex.Controls/ToggleButton.cs

@ -0,0 +1,28 @@
// -----------------------------------------------------------------------
// <copyright file="ToggleButton.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
public class ToggleButton : Button
{
public static readonly PerspexProperty<bool> IsCheckedProperty =
PerspexProperty.Register<ToggleButton, bool>("IsChecked");
public ToggleButton()
{
this.Click += (s, e) => this.IsChecked = !this.IsChecked;
this.AddPseudoClass(IsCheckedProperty, ":checked");
}
public bool IsChecked
{
get { return this.GetValue(IsCheckedProperty); }
set { this.SetValue(IsCheckedProperty, value); }
}
}
}

107
Perspex.Controls/TreeDataTemplate.cs

@ -0,0 +1,107 @@
// -----------------------------------------------------------------------
// <copyright file="TreeDataTemplate.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections;
public class TreeDataTemplate : DataTemplate
{
public TreeDataTemplate(
Func<object, Control> build,
Func<object, IEnumerable> itemsSelector)
: this(o => true, build, itemsSelector)
{
}
public TreeDataTemplate(
Type type,
Func<object, Control> build,
Func<object, IEnumerable> itemsSelector)
: this(o => IsInstance(o, type), build, itemsSelector)
{
}
public TreeDataTemplate(
Type type,
Func<object, Control> build,
Func<object, IEnumerable> itemsSelector,
Func<object, bool> isExpanded)
: this(o => IsInstance(o, type), build, itemsSelector, isExpanded)
{
}
public TreeDataTemplate(
Func<object, bool> match,
Func<object, Control> build,
Func<object, IEnumerable> itemsSelector)
: this(match, build, itemsSelector, _ => false)
{
this.ItemsSelector = itemsSelector;
}
public TreeDataTemplate(
Func<object, bool> match,
Func<object, Control> build,
Func<object, IEnumerable> itemsSelector,
Func<object, bool> isExpanded)
: base(match, build)
{
this.ItemsSelector = itemsSelector;
this.IsExpanded = isExpanded;
}
public Func<object, IEnumerable> ItemsSelector { get; private set; }
public Func<object, bool> IsExpanded { get; private set; }
}
public class TreeDataTemplate<T> : TreeDataTemplate
{
public TreeDataTemplate(
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector)
: base(typeof(T), Cast(build), Cast(itemsSelector))
{
}
public TreeDataTemplate(
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector,
Func<T, bool> isExpanded)
: base(typeof(T), Cast(build), Cast(itemsSelector), Cast(isExpanded))
{
}
public TreeDataTemplate(
Func<T, bool> match,
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector)
: base(CastMatch(match), Cast(build), Cast(itemsSelector))
{
}
public TreeDataTemplate(
Func<T, bool> match,
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector,
Func<T, bool> isExpanded)
: base(CastMatch(match), Cast(build), Cast(itemsSelector), Cast(isExpanded))
{
}
private static Func<object, bool> CastMatch(Func<T, bool> f)
{
return o => (o is T) ? f((T)o) : false;
}
private static Func<object, TResult> Cast<TResult>(Func<T, TResult> f)
{
return o => f((T)o);
}
}
}

78
Perspex.Controls/TreeView.cs

@ -0,0 +1,78 @@
// -----------------------------------------------------------------------
// <copyright file="TreeView.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Input;
public class TreeView : SelectingItemsControl
{
public TreeView()
{
this.PointerPressed += this.OnPointerPressed;
}
protected override Control CreateItemControlOverride(object item)
{
TreeViewItem result = item as TreeViewItem;
if (result == null)
{
TreeDataTemplate template = this.GetTreeDataTemplate(item);
result = new TreeViewItem
{
Header = template.Build(item),
Items = template.ItemsSelector(item),
IsExpanded = template.IsExpanded(item),
};
}
return result;
}
private TreeDataTemplate GetTreeDataTemplate(object item)
{
DataTemplate template = this.GetDataTemplate(item);
TreeDataTemplate treeTemplate = template as TreeDataTemplate;
if (treeTemplate == null)
{
treeTemplate = new TreeDataTemplate(template.Build, x => null);
}
return treeTemplate;
}
private void OnPointerPressed(object sender, PointerEventArgs e)
{
IVisual source = (IVisual)e.Source;
ContentPresenter contentPresenter = source.GetVisualAncestors()
.OfType<ContentPresenter>()
.FirstOrDefault();
if (contentPresenter != null)
{
TreeViewItem item = contentPresenter.TemplatedParent as TreeViewItem;
if (item != null)
{
foreach (var i in this.GetVisualDescendents().OfType<TreeViewItem>())
{
i.IsSelected = i == item;
}
this.SelectedItem = this.GetItemForControl(item);
}
}
}
}
}

56
Perspex.Controls/TreeViewItem.cs

@ -0,0 +1,56 @@
// -----------------------------------------------------------------------
// <copyright file="TreeViewItem.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System.Linq;
public class TreeViewItem : HeaderedItemsControl
{
public static readonly PerspexProperty<bool> IsExpandedProperty =
PerspexProperty.Register<TreeViewItem, bool>("IsExpanded");
public static readonly PerspexProperty<bool> IsSelectedProperty =
PerspexProperty.Register<TreeViewItem, bool>("IsSelected");
TreeView parent;
public TreeViewItem()
{
this.AddPseudoClass(IsSelectedProperty, ":selected");
AffectsRender(IsSelectedProperty);
}
public bool IsExpanded
{
get { return this.GetValue(IsExpandedProperty); }
set { this.SetValue(IsExpandedProperty, value); }
}
public bool IsSelected
{
get { return this.GetValue(IsSelectedProperty); }
set { this.SetValue(IsSelectedProperty, value); }
}
protected override Control CreateItemControlOverride(object item)
{
if (this.parent != null)
{
return this.parent.CreateItemControl(item);
}
else
{
return null;
}
}
protected override void OnVisualParentChanged(IVisual oldParent)
{
this.parent = this.GetVisualAncestors().OfType<TreeView>().FirstOrDefault();
}
}
}

10
Perspex.Controls/packages.config

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Interfaces" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Linq" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Main" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-PlatformServices" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Splat" version="1.3.3" targetFramework="portable-net45+win" />
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="portable-net45+win" developmentDependency="true" />
</packages>

4
Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj

@ -38,9 +38,9 @@
<Reference Include="Magick.NET-x86"> <Reference Include="Magick.NET-x86">
<HintPath>..\packages\Magick.NET-Q16-x86.6.8.9.101\lib\net40-client\Magick.NET-x86.dll</HintPath> <HintPath>..\packages\Magick.NET-Q16-x86.6.8.9.101\lib\net40-client\Magick.NET-x86.dll</HintPath>
</Reference> </Reference>
<Reference Include="Splat, Version=1.1.1.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Splat, Version=1.3.3.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Splat.1.1.1\lib\Net45\Splat.dll</HintPath> <HintPath>..\packages\Splat.1.3.3\lib\Net45\Splat.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />

2
Perspex.Direct2D1.RenderTests/packages.config

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Magick.NET-Q16-x86" version="6.8.9.101" targetFramework="net45" /> <package id="Magick.NET-Q16-x86" version="6.8.9.101" targetFramework="net45" />
<package id="Splat" version="1.1.1" targetFramework="net45" /> <package id="Splat" version="1.3.3" targetFramework="net45" />
</packages> </packages>

5
Perspex.Direct2D1/Perspex.Direct2D1.csproj

@ -39,8 +39,9 @@
<Reference Include="SharpDX.DXGI"> <Reference Include="SharpDX.DXGI">
<HintPath>..\packages\SharpDX.DXGI.2.5.0\lib\net40\SharpDX.DXGI.dll</HintPath> <HintPath>..\packages\SharpDX.DXGI.2.5.0\lib\net40\SharpDX.DXGI.dll</HintPath>
</Reference> </Reference>
<Reference Include="Splat"> <Reference Include="Splat, Version=1.3.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Splat.1.1.1\lib\Net45\Splat.dll</HintPath> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Splat.1.3.3\lib\Net45\Splat.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />

2
Perspex.Direct2D1/packages.config

@ -6,5 +6,5 @@
<package id="SharpDX" version="2.5.0" targetFramework="net45" /> <package id="SharpDX" version="2.5.0" targetFramework="net45" />
<package id="SharpDX.Direct2D1" version="2.5.0" targetFramework="net45" /> <package id="SharpDX.Direct2D1" version="2.5.0" targetFramework="net45" />
<package id="SharpDX.DXGI" version="2.5.0" targetFramework="net45" /> <package id="SharpDX.DXGI" version="2.5.0" targetFramework="net45" />
<package id="Splat" version="1.1.1" targetFramework="net45" /> <package id="Splat" version="1.3.3" targetFramework="net45" />
</packages> </packages>

47
Perspex.Input/FocusManager.cs

@ -0,0 +1,47 @@
// -----------------------------------------------------------------------
// <copyright file="IFocusManager.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using Perspex.Interactivity;
public class FocusManager : IFocusManager
{
public IInputElement Current
{
get;
private set;
}
public void Focus(IInputElement control)
{
IInteractive current = this.Current as IInteractive;
IInteractive next = control as IInteractive;
if (current != null)
{
current.RaiseEvent(new RoutedEventArgs
{
RoutedEvent = InputElement.LostFocusEvent,
Source = current,
OriginalSource = current,
});
}
this.Current = control;
if (next != null)
{
next.RaiseEvent(new RoutedEventArgs
{
RoutedEvent = InputElement.GotFocusEvent,
Source = next,
OriginalSource = next,
});
}
}
}
}

21
Perspex.Input/IFocusManager.cs

@ -0,0 +1,21 @@
// -----------------------------------------------------------------------
// <copyright file="IFocusManager.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public interface IFocusManager
{
IInputElement Current { get; }
void Focus(IInputElement focusable);
}
}

12
Perspex.Input/IInputDevice.cs

@ -0,0 +1,12 @@
// -----------------------------------------------------------------------
// <copyright file="IInputDevice.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
public interface IInputDevice
{
}
}

36
Perspex.Input/IInputElement.cs

@ -0,0 +1,36 @@
// -----------------------------------------------------------------------
// <copyright file="IInputElement.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using Perspex.Interactivity;
public interface IInputElement : IInteractive
{
event EventHandler<RoutedEventArgs> GotFocus;
event EventHandler<RoutedEventArgs> LostFocus;
event EventHandler<KeyEventArgs> KeyDown;
event EventHandler<PointerEventArgs> PointerEnter;
event EventHandler<PointerEventArgs> PointerLeave;
event EventHandler<PointerEventArgs> PointerPressed;
event EventHandler<PointerEventArgs> PointerReleased;
bool Focusable { get; }
bool IsFocused { get; }
bool IsPointerOver { get; }
void Focus();
}
}

21
Perspex.Input/IInputManager.cs

@ -0,0 +1,21 @@
// -----------------------------------------------------------------------
// <copyright file="IInputManager.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using Perspex.Input.Raw;
using Perspex.Layout;
public interface IInputManager
{
IObservable<RawInputEventArgs> RawEventReceived { get; }
void Process(RawInputEventArgs e);
void SetPointerOver(IPointerDevice device, IVisual visual, Point p);
}
}

33
Perspex.Input/IKeyboardDevice.cs

@ -0,0 +1,33 @@
// -----------------------------------------------------------------------
// <copyright file="IKeyboardDevice.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
[Flags]
public enum ModifierKeys
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8,
}
[Flags]
public enum KeyStates
{
None = 0,
Down = 1,
Toggled = 2,
}
public interface IKeyboardDevice : IInputDevice
{
ModifierKeys Modifiers { get; }
}
}

13
Perspex.Input/IMouseDevice.cs

@ -0,0 +1,13 @@
// -----------------------------------------------------------------------
// <copyright file="IMouseDevice.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
public interface IMouseDevice : IPointerDevice
{
Point Position { get; }
}
}

20
Perspex.Input/IPointerDevice.cs

@ -0,0 +1,20 @@
// -----------------------------------------------------------------------
// <copyright file="IPointerDevice.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using Perspex.Interactivity;
public interface IPointerDevice : IInputDevice
{
IInteractive Captured { get; }
void Capture(IInteractive control);
Point GetPosition(IVisual relativeTo);
}
}

121
Perspex.Input/InputElement.cs

@ -0,0 +1,121 @@
// -----------------------------------------------------------------------
// <copyright file="Control.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using Perspex.Interactivity;
using Splat;
public class InputElement : Interactive, IInputElement
{
public static readonly PerspexProperty<bool> FocusableProperty =
PerspexProperty.Register<InputElement, bool>("Focusable");
public static readonly PerspexProperty<bool> IsFocusedProperty =
PerspexProperty.Register<InputElement, bool>("IsFocused", false);
public static readonly PerspexProperty<bool> IsPointerOverProperty =
PerspexProperty.Register<InputElement, bool>("IsPointerOver");
public static readonly RoutedEvent<RoutedEventArgs> GotFocusEvent =
RoutedEvent.Register<InputElement, RoutedEventArgs>("GotFocus", RoutingStrategy.Bubble);
public static readonly RoutedEvent<RoutedEventArgs> LostFocusEvent =
RoutedEvent.Register<InputElement, RoutedEventArgs>("LostFocus", RoutingStrategy.Bubble);
public static readonly RoutedEvent<KeyEventArgs> KeyDownEvent =
RoutedEvent.Register<InputElement, KeyEventArgs>("KeyDown", RoutingStrategy.Bubble);
public static readonly RoutedEvent<KeyEventArgs> PreviewKeyDownEvent =
RoutedEvent.Register<InputElement, KeyEventArgs>("PreviewKeyDown", RoutingStrategy.Tunnel);
public static readonly RoutedEvent<PointerEventArgs> PointerEnterEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerEnter", RoutingStrategy.Direct);
public static readonly RoutedEvent<PointerEventArgs> PointerLeaveEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerLeave", RoutingStrategy.Direct);
public static readonly RoutedEvent<PointerEventArgs> PointerPressedEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerPressed", RoutingStrategy.Bubble);
public static readonly RoutedEvent<PointerEventArgs> PointerReleasedEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerReleased", RoutingStrategy.Bubble);
public InputElement()
{
this.GotFocus += (s, e) => this.IsFocused = true;
this.LostFocus += (s, e) => this.IsFocused = false;
this.PointerEnter += (s, e) => this.IsPointerOver = true;
this.PointerLeave += (s, e) => this.IsPointerOver = false;
}
public event EventHandler<RoutedEventArgs> GotFocus
{
add { this.AddHandler(GotFocusEvent, value); }
remove { this.RemoveHandler(GotFocusEvent, value); }
}
public event EventHandler<RoutedEventArgs> LostFocus
{
add { this.AddHandler(LostFocusEvent, value); }
remove { this.RemoveHandler(LostFocusEvent, value); }
}
public event EventHandler<KeyEventArgs> KeyDown
{
add { this.AddHandler(KeyDownEvent, value); }
remove { this.RemoveHandler(KeyDownEvent, value); }
}
public event EventHandler<PointerEventArgs> PointerEnter
{
add { this.AddHandler(PointerEnterEvent, value); }
remove { this.RemoveHandler(PointerEnterEvent, value); }
}
public event EventHandler<PointerEventArgs> PointerLeave
{
add { this.AddHandler(PointerLeaveEvent, value); }
remove { this.RemoveHandler(PointerLeaveEvent, value); }
}
public event EventHandler<PointerEventArgs> PointerPressed
{
add { this.AddHandler(PointerPressedEvent, value); }
remove { this.RemoveHandler(PointerPressedEvent, value); }
}
public event EventHandler<PointerEventArgs> PointerReleased
{
add { this.AddHandler(PointerReleasedEvent, value); }
remove { this.RemoveHandler(PointerReleasedEvent, value); }
}
public bool Focusable
{
get { return this.GetValue(FocusableProperty); }
set { this.SetValue(FocusableProperty, value); }
}
public bool IsFocused
{
get { return this.GetValue(IsFocusedProperty); }
private set { this.SetValue(IsFocusedProperty, value); }
}
public bool IsPointerOver
{
get { return this.GetValue(IsPointerOverProperty); }
internal set { this.SetValue(IsPointerOverProperty, value); }
}
public void Focus()
{
Locator.Current.GetService<IFocusManager>().Focus(this);
}
}
}

64
Perspex.Input/InputManager.cs

@ -0,0 +1,64 @@
// -----------------------------------------------------------------------
// <copyright file="InputManager.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using Perspex.Input.Raw;
public class InputManager : IInputManager
{
private List<IInputElement> pointerOvers = new List<IInputElement>();
private Subject<RawInputEventArgs> rawEventReceived = new Subject<RawInputEventArgs>();
public IObservable<RawInputEventArgs> RawEventReceived
{
get { return this.rawEventReceived; }
}
public void Process(RawInputEventArgs e)
{
this.rawEventReceived.OnNext(e);
}
public void SetPointerOver(IPointerDevice device, IVisual visual, Point p)
{
IEnumerable<IInputElement> hits = visual.GetVisualsAt(p).OfType<IInputElement>();
foreach (var control in this.pointerOvers.Except(hits).ToList())
{
PointerEventArgs e = new PointerEventArgs
{
RoutedEvent = InputElement.PointerLeaveEvent,
Device = device,
OriginalSource = control,
Source = control,
};
this.pointerOvers.Remove(control);
control.RaiseEvent(e);
}
foreach (var control in hits.Except(this.pointerOvers))
{
PointerEventArgs e = new PointerEventArgs
{
RoutedEvent = InputElement.PointerEnterEvent,
Device = device,
OriginalSource = control,
Source = control,
};
this.pointerOvers.Add(control);
control.RaiseEvent(e);
}
}
}
}

214
Perspex.Input/Key.cs

@ -0,0 +1,214 @@
// -----------------------------------------------------------------------
// <copyright file="Key.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
public enum Key
{
None = 0,
Cancel = 1,
Back = 2,
Tab = 3,
LineFeed = 4,
Clear = 5,
Return = 6,
Enter = 6,
Pause = 7,
CapsLock = 8,
Capital = 8,
HangulMode = 9,
KanaMode = 9,
JunjaMode = 10,
FinalMode = 11,
KanjiMode = 12,
HanjaMode = 12,
Escape = 13,
ImeConvert = 14,
ImeNonConvert = 15,
ImeAccept = 16,
ImeModeChange = 17,
Space = 18,
PageUp = 19,
Prior = 19,
PageDown = 20,
Next = 20,
End = 21,
Home = 22,
Left = 23,
Up = 24,
Right = 25,
Down = 26,
Select = 27,
Print = 28,
Execute = 29,
Snapshot = 30,
PrintScreen = 30,
Insert = 31,
Delete = 32,
Help = 33,
D0 = 34,
D1 = 35,
D2 = 36,
D3 = 37,
D4 = 38,
D5 = 39,
D6 = 40,
D7 = 41,
D8 = 42,
D9 = 43,
A = 44,
B = 45,
C = 46,
D = 47,
E = 48,
F = 49,
G = 50,
H = 51,
I = 52,
J = 53,
K = 54,
L = 55,
M = 56,
N = 57,
O = 58,
P = 59,
Q = 60,
R = 61,
S = 62,
T = 63,
U = 64,
V = 65,
W = 66,
X = 67,
Y = 68,
Z = 69,
LWin = 70,
RWin = 71,
Apps = 72,
Sleep = 73,
NumPad0 = 74,
NumPad1 = 75,
NumPad2 = 76,
NumPad3 = 77,
NumPad4 = 78,
NumPad5 = 79,
NumPad6 = 80,
NumPad7 = 81,
NumPad8 = 82,
NumPad9 = 83,
Multiply = 84,
Add = 85,
Separator = 86,
Subtract = 87,
Decimal = 88,
Divide = 89,
F1 = 90,
F2 = 91,
F3 = 92,
F4 = 93,
F5 = 94,
F6 = 95,
F7 = 96,
F8 = 97,
F9 = 98,
F10 = 99,
F11 = 100,
F12 = 101,
F13 = 102,
F14 = 103,
F15 = 104,
F16 = 105,
F17 = 106,
F18 = 107,
F19 = 108,
F20 = 109,
F21 = 110,
F22 = 111,
F23 = 112,
F24 = 113,
NumLock = 114,
Scroll = 115,
LeftShift = 116,
RightShift = 117,
LeftCtrl = 118,
RightCtrl = 119,
LeftAlt = 120,
RightAlt = 121,
BrowserBack = 122,
BrowserForward = 123,
BrowserRefresh = 124,
BrowserStop = 125,
BrowserSearch = 126,
BrowserFavorites = 127,
BrowserHome = 128,
VolumeMute = 129,
VolumeDown = 130,
VolumeUp = 131,
MediaNextTrack = 132,
MediaPreviousTrack = 133,
MediaStop = 134,
MediaPlayPause = 135,
LaunchMail = 136,
SelectMedia = 137,
LaunchApplication1 = 138,
LaunchApplication2 = 139,
OemSemicolon = 140,
Oem1 = 140,
OemPlus = 141,
OemComma = 142,
OemMinus = 143,
OemPeriod = 144,
OemQuestion = 145,
Oem2 = 145,
OemTilde = 146,
Oem3 = 146,
AbntC1 = 147,
AbntC2 = 148,
OemOpenBrackets = 149,
Oem4 = 149,
OemPipe = 150,
Oem5 = 150,
OemCloseBrackets = 151,
Oem6 = 151,
OemQuotes = 152,
Oem7 = 152,
Oem8 = 153,
OemBackslash = 154,
Oem102 = 154,
ImeProcessed = 155,
System = 156,
OemAttn = 157,
DbeAlphanumeric = 157,
OemFinish = 158,
DbeKatakana = 158,
DbeHiragana = 159,
OemCopy = 159,
DbeSbcsChar = 160,
OemAuto = 160,
DbeDbcsChar = 161,
OemEnlw = 161,
OemBackTab = 162,
DbeRoman = 162,
DbeNoRoman = 163,
Attn = 163,
CrSel = 164,
DbeEnterWordRegisterMode = 164,
ExSel = 165,
DbeEnterImeConfigureMode = 165,
EraseEof = 166,
DbeFlushString = 166,
Play = 167,
DbeCodeInput = 167,
DbeNoCodeInput = 168,
Zoom = 168,
NoName = 169,
DbeDetermineString = 169,
DbeEnterDialogConversionMode = 170,
Pa1 = 170,
OemClear = 171,
DeadCharProcessed = 172,
}
}

20
Perspex.Input/KeyEventArgs.cs

@ -0,0 +1,20 @@
// -----------------------------------------------------------------------
// <copyright file="PointerEventArgs.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using Perspex.Interactivity;
public class KeyEventArgs : RoutedEventArgs
{
public IKeyboardDevice Device { get; set; }
public Key Key { get; set; }
public string Text { get; set; }
}
}

75
Perspex.Input/KeyboardDevice.cs

@ -0,0 +1,75 @@
// -----------------------------------------------------------------------
// <copyright file="KeyboardDevice.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Input.Raw;
using Splat;
public abstract class KeyboardDevice : IKeyboardDevice
{
public KeyboardDevice()
{
this.InputManager.RawEventReceived
.OfType<RawKeyEventArgs>()
.Where(x => x.Device == this)
.Subscribe(this.ProcessRawEvent);
}
public IInputManager InputManager
{
get { return Locator.Current.GetService<IInputManager>(); }
}
public IFocusManager FocusManager
{
get { return Locator.Current.GetService<IFocusManager>(); }
}
public IInputElement FocusedElement
{
get;
protected set;
}
public abstract ModifierKeys Modifiers { get; }
private void ProcessRawEvent(RawKeyEventArgs e)
{
IInputElement element = this.FocusedElement;
if (element != null)
{
switch (e.Type)
{
case RawKeyEventType.KeyDown:
element.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.PreviewKeyDownEvent,
Device = this,
Key = e.Key,
Text = e.Text,
Source = element,
OriginalSource = element,
});
element.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Device = this,
Key = e.Key,
Text = e.Text,
Source = element,
OriginalSource = element,
});
break;
}
}
}
}
}

163
Perspex.Input/MouseDevice.cs

@ -0,0 +1,163 @@
// -----------------------------------------------------------------------
// <copyright file="MouseDevice.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Input.Raw;
using Perspex.Interactivity;
using Splat;
public abstract class MouseDevice : IMouseDevice
{
public MouseDevice()
{
this.InputManager.RawEventReceived
.OfType<RawMouseEventArgs>()
.Where(x => x.Device == this)
.Subscribe(this.ProcessRawEvent);
}
public IInteractive Captured
{
get;
protected set;
}
public IInputManager InputManager
{
get { return Locator.Current.GetService<IInputManager>(); }
}
public Point Position
{
get;
protected set;
}
public virtual void Capture(IInteractive control)
{
this.Captured = control;
}
public Point GetPosition(IVisual relativeTo)
{
Point p = this.GetClientPosition();
IVisual v = relativeTo;
while (v != null)
{
p -= v.Bounds.Position;
v = v.VisualParent;
}
return p;
}
protected abstract Point GetClientPosition();
private void ProcessRawEvent(RawMouseEventArgs e)
{
this.Position = e.Position;
switch (e.Type)
{
case RawMouseEventType.Move:
this.MouseMove((IMouseDevice)e.Device, (IVisual)e.Root, e.Position);
break;
case RawMouseEventType.LeftButtonDown:
this.MouseDown((IMouseDevice)e.Device, (IVisual)e.Root, e.Position);
break;
case RawMouseEventType.LeftButtonUp:
this.MouseUp((IMouseDevice)e.Device, (IVisual)e.Root, e.Position);
break;
}
}
private void MouseMove(IMouseDevice device, IVisual visual, Point p)
{
if (this.Captured == null)
{
this.InputManager.SetPointerOver(this, visual, p);
}
else
{
Point offset = new Point();
foreach (IVisual ancestor in this.Captured.GetVisualAncestors())
{
offset += ancestor.Bounds.Position;
}
this.InputManager.SetPointerOver(this, this.Captured, p - offset);
}
}
private void MouseDown(IMouseDevice device, IVisual visual, Point p)
{
IVisual hit = visual.GetVisualAt(p);
if (hit != null)
{
IInteractive source = this.GetSource(hit);
if (source != null)
{
source.RaiseEvent(new PointerEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerPressedEvent,
OriginalSource = source,
Source = source,
});
}
IInputElement focusable = this.GetFocusable(hit);
if (focusable != null && focusable.Focusable)
{
focusable.Focus();
}
}
}
private void MouseUp(IMouseDevice device, IVisual visual, Point p)
{
IVisual hit = visual.GetVisualAt(p);
if (hit != null)
{
IInteractive source = this.GetSource(hit);
if (source != null)
{
source.RaiseEvent(new PointerEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerReleasedEvent,
OriginalSource = source,
Source = source,
});
}
}
}
private IInputElement GetFocusable(IVisual hit)
{
return this.Captured as IInputElement ??
hit.GetSelfAndVisualAncestors().OfType<IInputElement>().FirstOrDefault(x => x.Focusable);
}
private IInteractive GetSource(IVisual hit)
{
return this.Captured ??
(hit as IInteractive) ??
hit.GetSelfAndVisualAncestors().OfType<IInteractive>().FirstOrDefault();
}
}
}

108
Perspex.Input/Perspex.Input.csproj

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{62024B2D-53EB-4638-B26B-85EEAA54866E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex.Input</RootNamespace>
<AssemblyName>Perspex.Input</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<ProjectReference Include="..\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="FocusManager.cs" />
<Compile Include="IFocusManager.cs" />
<Compile Include="IInputDevice.cs" />
<Compile Include="IInputElement.cs" />
<Compile Include="IInputManager.cs" />
<Compile Include="IKeyboardDevice.cs" />
<Compile Include="IMouseDevice.cs" />
<Compile Include="InputElement.cs" />
<Compile Include="InputManager.cs" />
<Compile Include="IPointerDevice.cs" />
<Compile Include="Key.cs" />
<Compile Include="KeyboardDevice.cs" />
<Compile Include="KeyEventArgs.cs" />
<Compile Include="MouseDevice.cs" />
<Compile Include="PointerEventArgs.cs" />
<Compile Include="Raw\RawInputEventArgs.cs" />
<Compile Include="Raw\RawKeyEventArgs.cs" />
<Compile Include="Raw\RawMouseEventArgs.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Splat">
<HintPath>..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Core">
<HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Interfaces">
<HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Linq">
<HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets" Condition="Exists('..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

21
Perspex.Input/PointerEventArgs.cs

@ -0,0 +1,21 @@
// -----------------------------------------------------------------------
// <copyright file="PointerEventArgs.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input
{
using System;
using Perspex.Interactivity;
public class PointerEventArgs : RoutedEventArgs
{
public IPointerDevice Device { get; set; }
public Point GetPosition(IVisual relativeTo)
{
return this.Device.GetPosition(relativeTo);
}
}
}

30
Perspex.Input/Properties/AssemblyInfo.cs

@ -0,0 +1,30 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Perspex.Input")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Perspex.Input")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

27
Perspex.Input/Raw/RawInputEventArgs.cs

@ -0,0 +1,27 @@
// -----------------------------------------------------------------------
// <copyright file="RawInputEventArgs.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input.Raw
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Perspex.Layout;
public class RawInputEventArgs : EventArgs
{
public RawInputEventArgs(IInputDevice device)
{
Contract.Requires<ArgumentNullException>(device != null);
this.Device = device;
}
public IInputDevice Device { get; private set; }
}
}

31
Perspex.Input/Raw/RawKeyEventArgs.cs

@ -0,0 +1,31 @@
// -----------------------------------------------------------------------
// <copyright file="RawKeyEventArgs.cs" company="Tricycle">
// Copyright 2014 Tricycle. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input.Raw
{
public enum RawKeyEventType
{
KeyDown,
KeyUp
}
public class RawKeyEventArgs : RawInputEventArgs
{
public RawKeyEventArgs(KeyboardDevice device, RawKeyEventType type, Key key, string text)
: base(device)
{
this.Key = key;
this.Type = type;
this.Text = text;
}
public Key Key { get; set; }
public string Text { get; set; }
public RawKeyEventType Type { get; set; }
}
}

42
Perspex.Input/Raw/RawMouseEventArgs.cs

@ -0,0 +1,42 @@
// -----------------------------------------------------------------------
// <copyright file="RawMouseEventArgs.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Input.Raw
{
using System;
using Perspex.Layout;
public enum RawMouseEventType
{
Move,
LeftButtonDown,
LeftButtonUp,
}
public class RawMouseEventArgs : RawInputEventArgs
{
public RawMouseEventArgs(
IInputDevice device,
ILayoutRoot root,
RawMouseEventType type,
Point position)
: base(device)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
this.Root = root;
this.Position = position;
this.Type = type;
}
public ILayoutRoot Root { get; private set; }
public Point Position { get; private set; }
public RawMouseEventType Type { get; private set; }
}
}

10
Perspex.Input/packages.config

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Interfaces" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Linq" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Main" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-PlatformServices" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Splat" version="1.3.3" targetFramework="portable-net45+win" />
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="portable-net45+win" developmentDependency="true" />
</packages>

13
Perspex.Interactivity/IInteractive.cs

@ -0,0 +1,13 @@
// -----------------------------------------------------------------------
// <copyright file="IInteractive.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Interactivity
{
public interface IInteractive : IVisual
{
void RaiseEvent(RoutedEventArgs e);
}
}

124
Perspex.Interactivity/Interactive.cs

@ -0,0 +1,124 @@
// -----------------------------------------------------------------------
// <copyright file="Interactive.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Interactivity
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using Perspex.Layout;
public class Interactive : Layoutable, IInteractive
{
private Dictionary<RoutedEvent, List<Delegate>> eventHandlers = new Dictionary<RoutedEvent, List<Delegate>>();
public void AddHandler(RoutedEvent routedEvent, Delegate handler)
{
Contract.Requires<NullReferenceException>(routedEvent != null);
Contract.Requires<NullReferenceException>(handler != null);
List<Delegate> delegates;
if (!this.eventHandlers.TryGetValue(routedEvent, out delegates))
{
delegates = new List<Delegate>();
this.eventHandlers.Add(routedEvent, delegates);
}
delegates.Add(handler);
}
public IObservable<EventPattern<T>> GetObservable<T>(RoutedEvent<T> routedEvent) where T : RoutedEventArgs
{
Contract.Requires<NullReferenceException>(routedEvent != null);
return Observable.FromEventPattern<T>(
handler => this.AddHandler(routedEvent, handler),
handler => this.RemoveHandler(routedEvent, handler));
}
public void RemoveHandler(RoutedEvent routedEvent, Delegate handler)
{
Contract.Requires<NullReferenceException>(routedEvent != null);
Contract.Requires<NullReferenceException>(handler != null);
List<Delegate> delegates;
if (this.eventHandlers.TryGetValue(routedEvent, out delegates))
{
delegates.Remove(handler);
}
}
public void RaiseEvent(RoutedEventArgs e)
{
Contract.Requires<NullReferenceException>(e != null);
if (e.RoutedEvent != null)
{
switch (e.RoutedEvent.RoutingStrategy)
{
case RoutingStrategy.Bubble:
this.BubbleEvent(e);
break;
case RoutingStrategy.Direct:
this.RaiseEventImpl(e);
break;
case RoutingStrategy.Tunnel:
this.TunnelEvent(e);
break;
}
}
}
private void BubbleEvent(RoutedEventArgs e)
{
Contract.Requires<NullReferenceException>(e != null);
foreach (var target in this.GetSelfAndVisualAncestors().OfType<Interactive>())
{
target.RaiseEventImpl(e);
if (e.Handled)
{
break;
}
}
}
private void TunnelEvent(RoutedEventArgs e)
{
Contract.Requires<NullReferenceException>(e != null);
foreach (var target in this.GetSelfAndVisualAncestors().OfType<Interactive>().Reverse())
{
target.RaiseEventImpl(e);
if (e.Handled)
{
break;
}
}
}
private void RaiseEventImpl(RoutedEventArgs e)
{
Contract.Requires<NullReferenceException>(e != null);
List<Delegate> delegates;
if (this.eventHandlers.TryGetValue(e.RoutedEvent, out delegates))
{
foreach (Delegate handler in delegates)
{
handler.DynamicInvoke(this, e);
}
}
}
}
}

90
Perspex.Interactivity/Perspex.Interactivity.csproj

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{6B0ED19D-A08B-461C-A9D9-A9EE40B0C06B}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex</RootNamespace>
<AssemblyName>Perspex.Interactive</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<ProjectReference Include="..\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="IInteractive.cs" />
<Compile Include="Interactive.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RoutedEvent.cs" />
<Compile Include="RoutedEventArgs.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Splat">
<HintPath>..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Core">
<HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Interfaces">
<HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Linq">
<HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets" Condition="Exists('..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

30
Perspex.Interactivity/Properties/AssemblyInfo.cs

@ -0,0 +1,30 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Perspex.Interactive")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Perspex.Interactive")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

97
Perspex.Interactivity/RoutedEvent.cs

@ -0,0 +1,97 @@
// -----------------------------------------------------------------------
// <copyright file="RoutedEvent.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Interactivity
{
using System;
using System.Reflection;
public enum RoutingStrategy
{
Tunnel,
Bubble,
Direct,
}
public class RoutedEvent
{
public RoutedEvent(
string name,
RoutingStrategy routingStrategy,
Type eventArgsType,
Type ownerType)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(eventArgsType != null);
Contract.Requires<NullReferenceException>(ownerType != null);
Contract.Requires<InvalidCastException>(typeof(RoutedEventArgs).GetTypeInfo().IsAssignableFrom(eventArgsType.GetTypeInfo()));
Contract.Requires<InvalidCastException>(typeof(Interactive).GetTypeInfo().IsAssignableFrom(ownerType.GetTypeInfo()));
this.EventArgsType = eventArgsType;
this.Name = name;
this.OwnerType = ownerType;
this.RoutingStrategy = routingStrategy;
}
public Type EventArgsType
{
get;
private set;
}
public string Name
{
get;
private set;
}
public Type OwnerType
{
get;
private set;
}
public RoutingStrategy RoutingStrategy
{
get;
private set;
}
public static RoutedEvent<TEventArgs> Register<TOwner, TEventArgs>(
string name,
RoutingStrategy routingStrategy)
where TOwner : Interactive
where TEventArgs : RoutedEventArgs
{
Contract.Requires<NullReferenceException>(name != null);
return new RoutedEvent<TEventArgs>(name, routingStrategy, typeof(TOwner));
}
public static RoutedEvent<TEventArgs> Register<TEventArgs>(
string name,
RoutingStrategy routingStrategy,
Type ownerType)
where TEventArgs : RoutedEventArgs
{
Contract.Requires<NullReferenceException>(name != null);
return new RoutedEvent<TEventArgs>(name, routingStrategy, ownerType);
}
}
public class RoutedEvent<TEventArgs> : RoutedEvent
where TEventArgs : RoutedEventArgs
{
public RoutedEvent(string name, RoutingStrategy routingStrategy, Type ownerType)
: base(name, routingStrategy, typeof(TEventArgs), ownerType)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(ownerType != null);
Contract.Requires<InvalidCastException>(typeof(Interactive).GetTypeInfo().IsAssignableFrom(ownerType.GetTypeInfo()));
}
}
}

36
Perspex.Interactivity/RoutedEventArgs.cs

@ -0,0 +1,36 @@
// -----------------------------------------------------------------------
// <copyright file="RoutedEventArgs.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Interactivity
{
using System;
public class RoutedEventArgs : EventArgs
{
public RoutedEventArgs()
{
}
public RoutedEventArgs(RoutedEvent routedEvent)
{
this.RoutedEvent = routedEvent;
}
public RoutedEventArgs(RoutedEvent routedEvent, IInteractive source)
{
this.RoutedEvent = routedEvent;
this.Source = source;
}
public bool Handled { get; set; }
public IInteractive OriginalSource { get; set; }
public RoutedEvent RoutedEvent { get; set; }
public IInteractive Source { get; set; }
}
}

10
Perspex.Interactivity/packages.config

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Interfaces" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Linq" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-Main" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Rx-PlatformServices" version="2.1.30214.0" targetFramework="portable-net45+win" />
<package id="Splat" version="1.3.3" targetFramework="portable-net45+win" />
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="portable-net45+win" developmentDependency="true" />
</packages>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save