215 changed files with 13070 additions and 435 deletions
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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)); |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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")] |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -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")] |
|||
@ -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> |
|||
@ -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)); |
|||
} |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
{ |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
{ |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
{ |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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")] |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
{ |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -1,5 +1,5 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages> |
|||
<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> |
|||
@ -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, |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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, |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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")] |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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")] |
|||
@ -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())); |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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…
Reference in new issue