diff --git a/Perspex.Base/Binding.cs b/Perspex.Base/Binding.cs new file mode 100644 index 0000000000..702b48ae1e --- /dev/null +++ b/Perspex.Base/Binding.cs @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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; + } + } +} diff --git a/Perspex.Base/Contract.cs b/Perspex.Base/Contract.cs new file mode 100644 index 0000000000..721d9f17bd --- /dev/null +++ b/Perspex.Base/Contract.cs @@ -0,0 +1,23 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + + public static class Contract + { + public static void Requires(bool condition) where TException : Exception, new() + { +#if DEBUG + if (!condition) + { + throw new TException(); + } +#endif + } + } +} diff --git a/Perspex.Base/IDescription.cs b/Perspex.Base/IDescription.cs new file mode 100644 index 0000000000..557c9c858f --- /dev/null +++ b/Perspex.Base/IDescription.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + public interface IDescription + { + string Description { get; } + } +} diff --git a/Perspex.Base/IObservableDescription.cs b/Perspex.Base/IObservableDescription.cs new file mode 100644 index 0000000000..fb7fb3e415 --- /dev/null +++ b/Perspex.Base/IObservableDescription.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + public interface IObservableDescription + { + string Description { get; } + } +} diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj new file mode 100644 index 0000000000..9603bdf991 --- /dev/null +++ b/Perspex.Base/Perspex.Base.csproj @@ -0,0 +1,85 @@ + + + + + 11.0 + Debug + AnyCPU + {B09B78D8-9B26-48B0-9149-D64A2F120F3F} + Library + Properties + Perspex + Perspex.Base + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + ..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll + + + ..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll + + + ..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll + + + ..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll + + + + + + + + + + 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}. + + + + + \ No newline at end of file diff --git a/Perspex.Base/PerspexList.cs b/Perspex.Base/PerspexList.cs new file mode 100644 index 0000000000..209699a388 --- /dev/null +++ b/Perspex.Base/PerspexList.cs @@ -0,0 +1,50 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Specialized; + using System.Reactive.Linq; + + public class PerspexList : ObservableCollection + { + public PerspexList() + { + this.Initialize(); + } + + public PerspexList(IEnumerable items) + : base(items) + { + this.Initialize(); + } + + public IObservable Changed + { + get; + private set; + } + + public void AddRange(IEnumerable items) + { + foreach (T item in items) + { + this.Add(item); + } + } + + private void Initialize() + { + this.Changed = Observable.FromEvent( + handler => (sender, e) => handler(e), + handler => this.CollectionChanged += handler, + handler => this.CollectionChanged -= handler); + } + } +} \ No newline at end of file diff --git a/Perspex.Base/PerspexObject.cs b/Perspex.Base/PerspexObject.cs new file mode 100644 index 0000000000..1cc3e4a65e --- /dev/null +++ b/Perspex.Base/PerspexObject.cs @@ -0,0 +1,646 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reactive.Linq; + using System.Reflection; + using Splat; + + + /// + /// The priority of a binding. + /// + public enum BindingPriority + { + /// + /// A local value. + /// + LocalValue, + + /// + /// A triggered style binding. + /// + /// + /// A style trigger is a selector such as .class which overrides a + /// 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. + /// + StyleTrigger, + + /// + /// A binding to a property on the templated parent. + /// + TemplatedParent, + + /// + /// A style binding. + /// + Style, + + /// + /// The binding is uninitialized. + /// + Unset = int.MaxValue, + } + + /// + /// An object with support. + /// + /// + /// This class is analogous to DependencyObject in WPF. + /// + public class PerspexObject : IEnableLogger + { + /// + /// The registered properties by type. + /// + private static Dictionary> registered = + new Dictionary>(); + + /// + /// The parent object that inherited values are inherited from. + /// + private PerspexObject inheritanceParent; + + /// + /// The set values/bindings on this object. + /// + private Dictionary values = + new Dictionary(); + + /// + /// Raised when a value changes on this object/ + /// + public event EventHandler PropertyChanged; + + /// + /// Gets or sets the parent object that inherited values + /// are inherited from. + /// + 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; + } + } + } + } + + + /// + /// Gets or sets the value of a . + /// + /// The property. + public object this[PerspexProperty property] + { + get { return this.GetValue(property); } + set { this.SetValue(property, value); } + } + + /// + /// Gets or sets a binding for a . + /// + /// The binding information. + 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; + } + } + } + + /// + /// Gets all s registered on a type. + /// + /// The type. + /// A collection of definitions. + public static IEnumerable GetProperties(Type type) + { + Contract.Requires(type != null); + + TypeInfo i = type.GetTypeInfo(); + + while (type != null) + { + List list; + + if (registered.TryGetValue(type, out list)) + { + foreach (PerspexProperty p in list) + { + yield return p; + } + } + + type = type.GetTypeInfo().BaseType; + } + } + + /// + /// Registers a on a type. + /// + /// The type. + /// The property. + /// + /// You won't usually want to call this method directly, instead use the + /// method. + /// + public static void Register(Type type, PerspexProperty property) + { + Contract.Requires(type != null); + Contract.Requires(property != null); + + List list; + + if (!registered.TryGetValue(type, out list)) + { + list = new List(); + registered.Add(type, list); + } + + if (!list.Contains(property)) + { + list.Add(property); + } + } + + /// + /// Clears a 's local value. + /// + /// The property. + public void ClearValue(PerspexProperty property) + { + Contract.Requires(property != null); + + this.SetValue(property, PerspexProperty.UnsetValue); + } + + /// + /// Gets an observable for a . + /// + /// The property. + /// An observable. + public IObservable GetObservable(PerspexProperty property) + { + Contract.Requires(property != null); + + return Observable.Create(observer => + { + EventHandler handler = (s, e) => + { + if (e.Property == property) + { + observer.OnNext(e.NewValue); + } + }; + + this.PropertyChanged += handler; + observer.OnNext(this.GetValue(property)); + + return () => + { + this.PropertyChanged -= handler; + }; + }); + } + + /// + /// Gets an observable for a . + /// + /// + /// + /// + public IObservable GetObservable(PerspexProperty property) + { + Contract.Requires(property != null); + + return this.GetObservable((PerspexProperty)property).Cast(); + } + + /// + /// Gets an observable for a . + /// + /// + /// + /// + public IObservable> GetObservableWithHistory(PerspexProperty property) + { + return Observable.Create>(observer => + { + EventHandler handler = (s, e) => + { + if (e.Property == property) + { + observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue)); + } + }; + + this.PropertyChanged += handler; + + return () => + { + this.PropertyChanged -= handler; + }; + }); + } + + /// + /// Gets a value. + /// + /// The property. + /// The value. + public object GetValue(PerspexProperty property) + { + Contract.Requires(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; + } + + /// + /// Gets a value. + /// + /// The property. + /// The value. + public T GetValue(PerspexProperty property) + { + Contract.Requires(property != null); + + return (T)this.GetValue((PerspexProperty)property); + } + + /// + /// Gets all of the values explicitly set on this object. + /// + public IEnumerable> GetSetValues() + { + foreach (var value in this.values) + { + yield return Tuple.Create( + value.Key, + value.Value.Value, + (BindingPriority)value.Value.ValuePriority); + } + } + + /// + /// Checks whether a is set on this object. + /// + /// The property. + /// True if the property is set, otherwise false. + public bool IsSet(PerspexProperty property) + { + Contract.Requires(property != null); + + return this.values.ContainsKey(property); + } + + /// + /// Checks whether a is registered on this class. + /// + /// The property. + /// True if the property is registered, otherwise false. + private bool IsRegistered(PerspexProperty property) + { + Type type = this.GetType(); + + while (type != null) + { + List list; + + if (registered.TryGetValue(type, out list)) + { + if (list.Contains(property)) + { + return true; + } + } + + type = type.GetTypeInfo().BaseType; + } + + return false; + } + + /// + /// Sets a value. + /// + /// The property. + /// The value. + public void SetValue(PerspexProperty property, object value) + { + Contract.Requires(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().StartWith(value), Priority); + } + + /// + /// Sets a value. + /// + /// The type of the property. + /// The property. + /// The value. + public void SetValue(PerspexProperty property, T value) + { + Contract.Requires(property != null); + + this.SetValue((PerspexProperty)property, value); + } + + /// + /// Binds a to an observable. + /// + /// The type of the property. + /// The property. + /// The observable. + /// The priority of the binding. + /// + /// A disposable which can be used to terminate the binding. + /// + public IDisposable Bind( + PerspexProperty property, + IObservable source, + BindingPriority priority = BindingPriority.LocalValue) + { + Contract.Requires(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); + } + } + + /// + /// Binds a to an observable. + /// + /// The type of the property. + /// The property. + /// The observable. + /// The priority of the binding. + /// + /// A disposable which can be used to terminate the binding. + /// + public IDisposable Bind( + PerspexProperty property, + IObservable source, + BindingPriority priority = BindingPriority.LocalValue) + { + Contract.Requires(property != null); + + return this.Bind((PerspexProperty)property, (IObservable)source, priority); + } + + /// + /// Initialites a two-way bind between s. + /// + /// The property on this object. + /// The source object. + /// The property on the source object. + /// + /// A disposable which can be used to terminate the binding. + /// + /// + /// The binding is first carried out from to this. Two-way + /// bindings are always at the LocalValue priority. + /// + 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; + } + + /// + /// Gets the default value for a property. + /// + /// The property. + /// The default value. + private object GetDefaultValue(PerspexProperty property) + { + if (property.Inherits && this.inheritanceParent != null) + { + return this.inheritanceParent.GetValue(property); + } + else + { + return property.GetDefaultValue(this.GetType()); + } + } + + /// + /// Called when a property is changed on the current . + /// + /// The event sender. + /// The event args. + /// + /// Checks for changes in an inherited property value. + /// + private void ParentPropertyChanged(object sender, PerspexPropertyChangedEventArgs e) + { + Contract.Requires(e != null); + + if (e.Property.Inherits && !this.IsSet(e.Property)) + { + this.RaisePropertyChanged(e.Property, e.OldValue, e.NewValue); + } + } + + /// + /// Raises the event. + /// + /// The property that has changed. + /// The old property value. + /// The new property value. + private void RaisePropertyChanged(PerspexProperty property, object oldValue, object newValue) + { + Contract.Requires(property != null); + + if (this.PropertyChanged != null) + { + PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs(this, property, oldValue, newValue); + property.NotifyChanged(e); + this.PropertyChanged(this, e); + } + } + } +} diff --git a/Perspex.Base/PerspexProperty.cs b/Perspex.Base/PerspexProperty.cs new file mode 100644 index 0000000000..05828ad6f9 --- /dev/null +++ b/Perspex.Base/PerspexProperty.cs @@ -0,0 +1,333 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Collections.Generic; + using System.Reactive.Subjects; + using System.Reflection; + + /// + /// A perspex property. + /// + /// + /// This class is analogous to DependencyProperty in WPF. + /// + public class PerspexProperty + { + /// + /// Represents an unset property value. + /// + public static readonly object UnsetValue = new Unset(); + + /// + /// The default values for the property, by type. + /// + private Dictionary defaultValues = new Dictionary(); + + /// + /// Observable fired when this property changes on any . + /// + private Subject changed = new Subject(); + + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The type of the property's value. + /// The type of the class that registers the property. + /// The default value of the property. + /// Whether the property inherits its value. + /// The default binding mode for the property. + public PerspexProperty( + string name, + Type valueType, + Type ownerType, + object defaultValue, + bool inherits, + BindingMode defaultBindingMode) + { + Contract.Requires(name != null); + Contract.Requires(valueType != null); + Contract.Requires(ownerType != null); + + this.Name = name; + this.PropertyType = valueType; + this.OwnerType = ownerType; + this.Inherits = inherits; + this.DefaultBindingMode = defaultBindingMode; + this.defaultValues.Add(ownerType, defaultValue); + } + + /// + /// Gets the name of the property. + /// + public string Name { get; private set; } + + /// + /// Gets the type of the property's value. + /// + public Type PropertyType { get; private set; } + + /// + /// Gets the type of the class that registers the property. + /// + public Type OwnerType { get; private set; } + + /// + /// Gets a value indicating whether the property inherits its value. + /// + public bool Inherits { get; private set; } + + /// + /// Gets the default binding mode for the property. + /// + /// + public BindingMode DefaultBindingMode { get; private set; } + + /// + /// Gets an observable that is fired when this property changes on any + /// instance. + /// + public IObservable Changed + { + get { return this.changed; } + } + + /// + /// Registers a . + /// + /// The type of the class that is registering the property. + /// The type of the property's value. + /// The name of the property. + /// The default value of the property. + /// Whether the property inherits its value. + /// The default binding mode for the property. + /// A + public static PerspexProperty Register( + string name, + TValue defaultValue = default(TValue), + bool inherits = false, + BindingMode defaultBindingMode = BindingMode.OneWay) + where TOwner : PerspexObject + { + Contract.Requires(name != null); + + PerspexProperty result = new PerspexProperty( + name, + typeof(TOwner), + defaultValue, + inherits, + defaultBindingMode); + + PerspexObject.Register(typeof(TOwner), result); + + return result; + } + + /// + /// Registers an attached . + /// + /// The type of the class that is registering the property. + /// The type of the class that the property is to be registered on. + /// The type of the property's value. + /// The name of the property. + /// The default value of the property. + /// Whether the property inherits its value. + /// The default binding mode for the property. + /// A + public static PerspexProperty RegisterAttached( + string name, + TValue defaultValue = default(TValue), + bool inherits = false, + BindingMode defaultBindingMode = BindingMode.OneWay) + where TOwner : PerspexObject + { + Contract.Requires(name != null); + + PerspexProperty result = new PerspexProperty( + name, + typeof(TOwner), + defaultValue, + inherits, + defaultBindingMode); + + PerspexObject.Register(typeof(THost), result); + + return result; + } + + /// + /// Provides access to a property's binding via the + /// indexer. + /// + /// The property. + /// A describing the binding. + public static Binding operator!(PerspexProperty property) + { + return new Binding + { + Priority = BindingPriority.LocalValue, + Property = property, + }; + } + + /// + /// Provides access to a property's template binding via the + /// indexer. + /// + /// The property. + /// A describing the binding. + public static Binding operator ~(PerspexProperty property) + { + return new Binding + { + Priority = BindingPriority.TemplatedParent, + Property = property, + }; + } + + /// + /// Returns a binding accessor that can be passed to 's [] + /// operator to initiate a binding. + /// + /// A . + /// + /// The ! and ~ operators are short forms of this. + /// + public Binding Bind() + { + return new Binding + { + Property = this, + }; + } + + /// + /// Gets the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public object GetDefaultValue(Type type) + { + Contract.Requires(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()); + } + + /// + /// Gets the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public void OverrideDefaultValue(Type type, object defaultValue) + { + Contract.Requires(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}"; + } + } + } + + /// + /// A typed perspex property. + /// + public class PerspexProperty : PerspexProperty + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The type of the class that registers the property. + /// The default value of the property. + /// Whether the property inherits its value. + /// The default binding mode for the property. + public PerspexProperty( + string name, + Type ownerType, + TValue defaultValue, + bool inherits, + BindingMode defaultBindingMode) + : base(name, typeof(TValue), ownerType, defaultValue, inherits, defaultBindingMode) + { + Contract.Requires(name != null); + Contract.Requires(ownerType != null); + } + + /// + /// Registers the property on another type. + /// + /// The type of the additional owner. + /// The property. + public PerspexProperty AddOwner() + { + PerspexObject.Register(typeof(TOwner), this); + return this; + } + + /// + /// Gets the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public TValue GetDefaultValue() + { + return (TValue)this.GetDefaultValue(typeof(T)); + } + } +} diff --git a/Perspex.Base/PerspexPropertyChangedEventArgs.cs b/Perspex.Base/PerspexPropertyChangedEventArgs.cs new file mode 100644 index 0000000000..4cfc915df8 --- /dev/null +++ b/Perspex.Base/PerspexPropertyChangedEventArgs.cs @@ -0,0 +1,47 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + /// + /// Provides information for a perspex property change. + /// + 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; + } + + /// + /// Gets the that the property changed on. + /// + /// + public PerspexObject Sender { get; private set; } + + /// + /// Gets the property that changed. + /// + public PerspexProperty Property { get; private set; } + + /// + /// Gets the old value of the property. + /// + public object OldValue { get; private set; } + + /// + /// Gets the new value of the property. + /// + public object NewValue { get; private set; } + } +} diff --git a/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/Perspex.Base/Platform/IPlatformThreadingInterface.cs new file mode 100644 index 0000000000..fa71e33956 --- /dev/null +++ b/Perspex.Base/Platform/IPlatformThreadingInterface.cs @@ -0,0 +1,20 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + using System; + using Perspex.Threading; + + public interface IPlatformThreadingInterface + { + Dispatcher GetThreadDispatcher(); + + void KillTimer(object timerHandle); + + object StartTimer(TimeSpan interval, Action internalTick); + } +} diff --git a/Perspex.Base/PriorityValue.cs b/Perspex.Base/PriorityValue.cs new file mode 100644 index 0000000000..9c843430e4 --- /dev/null +++ b/Perspex.Base/PriorityValue.cs @@ -0,0 +1,405 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Collections.Generic; + using System.Reactive.Disposables; + using System.Reactive.Subjects; + using System.Reflection; + + /// + /// Maintains a list of prioritised bindings together with a current value. + /// + /// + /// Bindings, in the form of s are added to the object using + /// the method. With the observable is passed a priority, where lower values + /// represent higher priorites. The current is selected from the highest + /// priority binding that doesn't return . 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 + /// observable is fired with the old and new values. + /// + public class PriorityValue + { + /// + /// The name of the property. + /// + private string name; + + /// + /// The value type. + /// + private Type valueType; + + /// + /// The currently registered binding entries. + /// + private LinkedList bindings = new LinkedList(); + + /// + /// The changed observable. + /// + private Subject> changed = new Subject>(); + + /// + /// The current value. + /// + private object value; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The value type. + public PriorityValue(string name, Type valueType) + { + this.name = name; + this.valueType = valueType; + this.value = PerspexProperty.UnsetValue; + this.ValuePriority = int.MaxValue; + } + + /// + /// Fired whenever the current changes to a new distinct value. + /// + public IObservable> Changed + { + get { return this.changed; } + } + + /// + /// Gets the current value. + /// + public object Value + { + get { return this.value; } + } + + /// + /// Gets the priority of the binding that is currently active. + /// + public int ValuePriority + { + get; + private set; + } + + /// + /// Checks whether a value is valid for a type. + /// + /// + /// + /// + 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; + } + + /// + /// Adds a new binding. + /// + /// The binding. + /// The binding priority. + /// + /// A disposable that will remove the binding. + /// + public IDisposable Add(IObservable binding, int priority) + { + BindingEntry entry = new BindingEntry(); + LinkedListNode 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); + }); + } + + /// + /// Adds a new binding, replacing all those of the same priority. + /// + /// The binding. + /// The binding priority. + /// + /// A disposable that will remove the binding. + /// + public IDisposable Replace(IObservable binding, int priority) + { + BindingEntry entry = new BindingEntry(); + LinkedListNode insert = this.bindings.First; + + while (insert != null && insert.Value.Priority < priority) + { + insert = insert.Next; + } + + while (insert != null && insert.Value.Priority == priority) + { + LinkedListNode 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); + }); + } + + /// + /// Removes all bindings with the specified priority. + /// + /// The priority. + public void Clear(int priority) + { + LinkedListNode item = this.bindings.First; + bool removed = false; + + while (item != null && item.Value.Priority <= priority) + { + LinkedListNode 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(); + } + } + + /// + /// Gets the currently active bindings on this object. + /// + /// An enumerable collection of bindings. + public IEnumerable GetBindings() + { + return this.bindings; + } + + /// + /// Called when a binding's value changes. + /// + /// The changed entry. + private void EntryChanged(BindingEntry changed) + { + if (changed.Priority <= this.ValuePriority) + { + this.UpdateValue(); + } + } + + /// + /// Called when a binding completes. + /// + /// The completed entry. + private void EntryCompleted(BindingEntry entry) + { + this.Remove(entry); + } + + /// + /// Sets the current value and notifies all observers. + /// + /// The new value. + /// The priority of the binding which produced the value. + 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.Default.Equals(old, value)) + { + this.value = value; + this.changed.OnNext(Tuple.Create(old, value)); + } + } + + /// + /// Removes the specified binding entry and updates the current value. + /// + /// The binding entry to remove. + private void Remove(BindingEntry entry) + { + entry.Dispose(); + this.bindings.Remove(entry); + this.UpdateValue(); + } + + /// + /// Updates the current value. + /// + 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); + } + + /// + /// A registered binding. + /// + public class BindingEntry : IDisposable + { + /// + /// The binding subscription. + /// + private IDisposable subscription; + + /// + /// Gets a description of the binding. + /// + public string Description + { + get; + private set; + } + + /// + /// The priority of the binding. + /// + public int Priority + { + get; + private set; + } + + /// + /// The current value of the binding. + /// + public object Value + { + get; + private set; + } + + /// + /// Starts listening to the specified binding. + /// + /// The binding. + /// The binding priority. + /// Called when the binding changes. + /// Called when the binding completes. + public void Start( + IObservable binding, + int priority, + Action changed, + Action completed) + { + Contract.Requires(binding != null); + Contract.Requires(changed != null); + Contract.Requires(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)); + } + + /// + /// Ends the binding subscription. + /// + public void Dispose() + { + if (this.subscription != null) + { + this.subscription.Dispose(); + } + } + } + } +} diff --git a/Perspex.Base/Properties/AssemblyInfo.cs b/Perspex.Base/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..79615c23a0 --- /dev/null +++ b/Perspex.Base/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Base")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Base")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Perspex.Base/Threading/Dispatcher.cs b/Perspex.Base/Threading/Dispatcher.cs new file mode 100644 index 0000000000..8a832c4b6a --- /dev/null +++ b/Perspex.Base/Threading/Dispatcher.cs @@ -0,0 +1,102 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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().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); + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/DispatcherFrame.cs b/Perspex.Base/Threading/DispatcherFrame.cs new file mode 100644 index 0000000000..4cb1b3d97a --- /dev/null +++ b/Perspex.Base/Threading/DispatcherFrame.cs @@ -0,0 +1,25 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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; } + } +} diff --git a/Perspex.Base/Threading/DispatcherOperation.cs b/Perspex.Base/Threading/DispatcherOperation.cs new file mode 100644 index 0000000000..e9c04c9dbe --- /dev/null +++ b/Perspex.Base/Threading/DispatcherOperation.cs @@ -0,0 +1,159 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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); + } + } + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/DispatcherTimer.cs b/Perspex.Base/Threading/DispatcherTimer.cs new file mode 100644 index 0000000000..c1289107bc --- /dev/null +++ b/Perspex.Base/Threading/DispatcherTimer.cs @@ -0,0 +1,140 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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(); + this.timerHandle = threading.StartTimer(this.Interval, this.InternalTick); + } + } + + public void Stop() + { + if (this.IsEnabled) + { + IPlatformThreadingInterface threading = Locator.Current.GetService(); + 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); + } + } + } +} \ No newline at end of file diff --git a/Perspex.Base/packages.config b/Perspex.Base/packages.config new file mode 100644 index 0000000000..af8b5868bf --- /dev/null +++ b/Perspex.Base/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Perspex.UnitTests/Controls/ContentControlTests.cs b/Perspex.Controls.UnitTests/ContentControlTests.cs similarity index 95% rename from Perspex.UnitTests/Controls/ContentControlTests.cs rename to Perspex.Controls.UnitTests/ContentControlTests.cs index 1a291f00a1..2b78ae3ed8 100644 --- a/Perspex.UnitTests/Controls/ContentControlTests.cs +++ b/Perspex.Controls.UnitTests/ContentControlTests.cs @@ -4,7 +4,7 @@ // // ----------------------------------------------------------------------- -namespace Perspex.UnitTests.Controls +namespace Perspex.Controls.UnitTests { using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -12,7 +12,6 @@ namespace Perspex.UnitTests.Controls using Perspex.Controls; using Perspex.Layout; using Perspex.Styling; - using Perspex.Themes.Default; using Splat; [TestClass] @@ -116,7 +115,7 @@ namespace Perspex.UnitTests.Controls target.Content = child; this.ApplyTemplate(target); - var contentPresenter = (ContentPresenter)((IVisual)child).VisualParent; + var contentPresenter = child.GetVisualParent(); Assert.AreEqual(target, contentPresenter.TemplatedParent); } @@ -145,7 +144,7 @@ namespace Perspex.UnitTests.Controls Border border = new Border(); border.Background = new Perspex.Media.SolidColorBrush(0xffffffff); ContentPresenter contentPresenter = new ContentPresenter(); - contentPresenter.TemplateBinding(parent, ContentPresenter.ContentProperty); + ////contentPresenter[~ContentPresenter.ContentProperty] = parent[~ContentPresenter.ContentProperty]; border.Content = contentPresenter; return border; }); diff --git a/Perspex.UnitTests/Controls/ControlTests.cs b/Perspex.Controls.UnitTests/ControlTests.cs similarity index 80% rename from Perspex.UnitTests/Controls/ControlTests.cs rename to Perspex.Controls.UnitTests/ControlTests.cs index 493f50227d..48be41d952 100644 --- a/Perspex.UnitTests/Controls/ControlTests.cs +++ b/Perspex.Controls.UnitTests/ControlTests.cs @@ -4,7 +4,7 @@ // // ----------------------------------------------------------------------- -namespace Perspex.UnitTests.Controls +namespace Perspex.Controls.UnitTests { using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -25,27 +25,6 @@ namespace Perspex.UnitTests.Controls Assert.AreEqual(0, target.Classes.Count); } - [TestMethod] - public void Setting_IsPointerOver_Should_Add_PointerOver_Class() - { - var target = new Control(); - - target.IsPointerOver = true; - - CollectionAssert.AreEqual(new[] { ":pointerover" }, target.Classes.ToArray()); - } - - [TestMethod] - public void Clearing_IsPointerOver_Should_Remove_PointerOver_Class() - { - var target = new Control(); - - target.IsPointerOver = true; - target.IsPointerOver = false; - - Assert.AreEqual(0, target.Classes.Count); - } - [TestMethod] public void Adding_Control_To_ILayoutRoot_Should_Style_Control() { diff --git a/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj new file mode 100644 index 0000000000..0e234939cb --- /dev/null +++ b/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -0,0 +1,125 @@ + + + + Debug + AnyCPU + {5CCB5571-7C30-4E7D-967D-0E2158EBD91F} + Library + Properties + Perspex.Controls.UnitTests + Perspex.Controls.UnitTests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Moq.4.2.1312.1622\lib\net40\Moq.dll + + + ..\packages\Splat.1.3.3\lib\Net45\Splat.dll + + + + + + + + + + + + + + + + + + + + + + + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/Perspex.Controls.UnitTests/Properties/AssemblyInfo.cs b/Perspex.Controls.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3f0c048257 --- /dev/null +++ b/Perspex.Controls.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Controls.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Controls.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0430478f-6126-4390-b94b-c02d91fd4f7c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Perspex.UnitTests/Controls/TemplatedControlTests.cs b/Perspex.Controls.UnitTests/TemplatedControlTests.cs similarity index 98% rename from Perspex.UnitTests/Controls/TemplatedControlTests.cs rename to Perspex.Controls.UnitTests/TemplatedControlTests.cs index 1f033f7f60..bb60e46d11 100644 --- a/Perspex.UnitTests/Controls/TemplatedControlTests.cs +++ b/Perspex.Controls.UnitTests/TemplatedControlTests.cs @@ -4,7 +4,7 @@ // // ----------------------------------------------------------------------- -namespace Perspex.UnitTests.Controls +namespace Perspex.Controls.UnitTests { using System; using System.Collections.Generic; diff --git a/Perspex.UnitTests/TestRoot.cs b/Perspex.Controls.UnitTests/TestRoot.cs similarity index 80% rename from Perspex.UnitTests/TestRoot.cs rename to Perspex.Controls.UnitTests/TestRoot.cs index 60df5ed428..ab9c547f27 100644 --- a/Perspex.UnitTests/TestRoot.cs +++ b/Perspex.Controls.UnitTests/TestRoot.cs @@ -4,13 +4,9 @@ // // ----------------------------------------------------------------------- -namespace Perspex.UnitTests +namespace Perspex.Controls.UnitTests { - using System; - using System.Collections.Generic; - using System.Reactive.Disposables; using Moq; - using Perspex.Controls; using Perspex.Layout; internal class TestRoot : Decorator, ILayoutRoot diff --git a/Perspex.Controls.UnitTests/packages.config b/Perspex.Controls.UnitTests/packages.config new file mode 100644 index 0000000000..3b7c7a697a --- /dev/null +++ b/Perspex.Controls.UnitTests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Perspex.Controls/Border.cs b/Perspex.Controls/Border.cs new file mode 100644 index 0000000000..bd0be84e28 --- /dev/null +++ b/Perspex.Controls/Border.cs @@ -0,0 +1,62 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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)); + } + } +} diff --git a/Perspex.Controls/Button.cs b/Perspex.Controls/Button.cs new file mode 100644 index 0000000000..95462d27fc --- /dev/null +++ b/Perspex.Controls/Button.cs @@ -0,0 +1,49 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using Perspex.Interactivity; + + public class Button : ContentControl + { + public static readonly RoutedEvent ClickEvent = + RoutedEvent.Register("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 Click + { + add { this.AddHandler(ClickEvent, value); } + remove { this.RemoveHandler(ClickEvent, value); } + } + } +} diff --git a/Perspex.Controls/CheckBox.cs b/Perspex.Controls/CheckBox.cs new file mode 100644 index 0000000000..a78e5ab7ae --- /dev/null +++ b/Perspex.Controls/CheckBox.cs @@ -0,0 +1,12 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public class CheckBox : ToggleButton + { + } +} diff --git a/Perspex.Controls/ColumnDefinition.cs b/Perspex.Controls/ColumnDefinition.cs new file mode 100644 index 0000000000..a07d2d45e2 --- /dev/null +++ b/Perspex.Controls/ColumnDefinition.cs @@ -0,0 +1,53 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public class ColumnDefinition : DefinitionBase + { + public static readonly PerspexProperty MaxWidthProperty = + PerspexProperty.Register("MaxWidth", double.PositiveInfinity); + + public static readonly PerspexProperty MinWidthProperty = + PerspexProperty.Register("MinWidth"); + + public static readonly PerspexProperty WidthProperty = + PerspexProperty.Register("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); } + } + } +} diff --git a/Perspex.Controls/ColumnDefinitions.cs b/Perspex.Controls/ColumnDefinitions.cs new file mode 100644 index 0000000000..73656b8854 --- /dev/null +++ b/Perspex.Controls/ColumnDefinitions.cs @@ -0,0 +1,12 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public class ColumnDefinitions : PerspexList + { + } +} diff --git a/Perspex.Controls/ContentControl.cs b/Perspex.Controls/ContentControl.cs new file mode 100644 index 0000000000..cd8ae71c6d --- /dev/null +++ b/Perspex.Controls/ContentControl.cs @@ -0,0 +1,24 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections.Generic; + using System.Linq; + + public class ContentControl : TemplatedControl + { + public static readonly PerspexProperty ContentProperty = + PerspexProperty.Register("Content"); + + public object Content + { + get { return this.GetValue(ContentProperty); } + set { this.SetValue(ContentProperty, value); } + } + } +} diff --git a/Perspex.Controls/ContentPresenter.cs b/Perspex.Controls/ContentPresenter.cs new file mode 100644 index 0000000000..6185df26ef --- /dev/null +++ b/Perspex.Controls/ContentPresenter.cs @@ -0,0 +1,151 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 ContentProperty = + ContentControl.ContentProperty.AddOwner(); + + public static readonly PerspexProperty> DataTemplateProperty = + PerspexProperty.Register>("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 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(); + } + } + + 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 content) + { + var children = this.CreateVisualChildren(); + this.ClearVisualChildren(); + this.AddVisualChildren(children); + this.InvalidateMeasure(); + } + } +} diff --git a/Perspex.Controls/Control.cs b/Perspex.Controls/Control.cs new file mode 100644 index 0000000000..4055e74c59 --- /dev/null +++ b/Perspex.Controls/Control.cs @@ -0,0 +1,270 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 BackgroundProperty = + PerspexProperty.Register("Background", inherits: true); + + public static readonly PerspexProperty BorderBrushProperty = + PerspexProperty.Register("BorderBrush"); + + public static readonly PerspexProperty BorderThicknessProperty = + PerspexProperty.Register("BorderThickness"); + + public static readonly PerspexProperty FontSizeProperty = + PerspexProperty.Register( + "FontSize", + defaultValue: 12.0, + inherits: true); + + public static readonly PerspexProperty ForegroundProperty = + PerspexProperty.Register("Foreground", new SolidColorBrush(0xff000000), true); + + public static readonly PerspexProperty ParentProperty = + PerspexProperty.Register("Parent"); + + public static readonly PerspexProperty TemplatedParentProperty = + PerspexProperty.Register("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.LogicalChildren + { + get { return Enumerable.Empty(); } + } + + protected override void OnAttachedToVisualTree(IRenderRoot root) + { + IStyler styler = Locator.Current.GetService(); + styler.ApplyStyles(this); + } + + protected void AddPseudoClass(PerspexProperty 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(); + + 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; + } + } +} diff --git a/Perspex.Controls/ControlExtensions.cs b/Perspex.Controls/ControlExtensions.cs new file mode 100644 index 0000000000..0a7bdcabaa --- /dev/null +++ b/Perspex.Controls/ControlExtensions.cs @@ -0,0 +1,37 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 GetTemplateControls(this ITemplatedControl control) + { + return GetTemplateControls(control, (IVisual)control); + } + + public static IEnumerable GetTemplateControls(ITemplatedControl templated, IVisual parent) + { + IVisual visual = parent as IVisual; + + foreach (IVisual child in visual.VisualChildren.OfType().Where(x => x.TemplatedParent == templated)) + { + yield return (Control)child; + + foreach (IVisual grandchild in GetTemplateControls(templated, child)) + { + yield return (Control)grandchild; + } + } + } + } +} diff --git a/Perspex.Controls/ControlTemplate.cs b/Perspex.Controls/ControlTemplate.cs new file mode 100644 index 0000000000..95b80c7814 --- /dev/null +++ b/Perspex.Controls/ControlTemplate.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using Perspex.Styling; + + public class ControlTemplate + { + private Func build; + + public ControlTemplate(Func build) + { + Contract.Requires(build != null); + + this.build = build; + } + + public static ControlTemplate Create(Func build) + where TControl : ITemplatedControl + { + Contract.Requires(build != null); + + return new ControlTemplate(c => build((TControl)c)); + } + + public Control Build(ITemplatedControl templatedParent) + { + Contract.Requires(templatedParent != null); + + Control root = this.build(templatedParent); + root.TemplatedParent = templatedParent; + return root; + } + } +} diff --git a/Perspex.Controls/Controls.cs b/Perspex.Controls/Controls.cs new file mode 100644 index 0000000000..3e0667cc17 --- /dev/null +++ b/Perspex.Controls/Controls.cs @@ -0,0 +1,22 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Perspex.Controls +{ + public class Controls : PerspexList + { + public Controls() + { + } + + public Controls(IEnumerable items) + : base(items) + { + } + } +} diff --git a/Perspex.Controls/DataTemplate.cs b/Perspex.Controls/DataTemplate.cs new file mode 100644 index 0000000000..9f9de2b477 --- /dev/null +++ b/Perspex.Controls/DataTemplate.cs @@ -0,0 +1,68 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 build) + : this(o => true, build) + { + } + + public DataTemplate(Type type, Func build) + : this(o => IsInstance(o, type), build) + { + } + + public DataTemplate(Func match, Func build) + { + Contract.Requires(match != null); + Contract.Requires(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 Match { get; private set; } + + public Func Build { get; private set; } + } + + public class DataTemplate : DataTemplate + { + public DataTemplate(Func build) + : base(typeof(T), Cast(build)) + { + } + + public DataTemplate(Func match, Func build) + : base(CastMatch(match), Cast(build)) + { + } + + private static Func CastMatch(Func f) + { + return o => (o is T) ? f((T)o) : false; + } + + private static Func Cast(Func f) + { + return o => f((T)o); + } + } +} diff --git a/Perspex.Controls/DataTemplates.cs b/Perspex.Controls/DataTemplates.cs new file mode 100644 index 0000000000..1f81fde90f --- /dev/null +++ b/Perspex.Controls/DataTemplates.cs @@ -0,0 +1,20 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 + { + } +} diff --git a/Perspex.Controls/Decorator.cs b/Perspex.Controls/Decorator.cs new file mode 100644 index 0000000000..a9d48ca80b --- /dev/null +++ b/Perspex.Controls/Decorator.cs @@ -0,0 +1,65 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 ContentProperty = + PerspexProperty.Register("Content"); + + public static readonly PerspexProperty PaddingProperty = + PerspexProperty.Register("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); + } + } +} diff --git a/Perspex.Controls/DefinitionBase.cs b/Perspex.Controls/DefinitionBase.cs new file mode 100644 index 0000000000..f36cab0052 --- /dev/null +++ b/Perspex.Controls/DefinitionBase.cs @@ -0,0 +1,20 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public class DefinitionBase : PerspexObject + { + public static readonly PerspexProperty SharedSizeGroupProperty = + PerspexProperty.Register("SharedSizeGroup", inherits: true); + + public string SharedSizeGroup + { + get { return this.GetValue(SharedSizeGroupProperty); } + set { this.SetValue(SharedSizeGroupProperty, value); } + } + } +} diff --git a/Perspex.Controls/Grid.cs b/Perspex.Controls/Grid.cs new file mode 100644 index 0000000000..4cc626b139 --- /dev/null +++ b/Perspex.Controls/Grid.cs @@ -0,0 +1,805 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections.Generic; + using System.Linq; + + public class Grid : Panel + { + public static readonly PerspexProperty ColumnProperty = + PerspexProperty.RegisterAttached("Column"); + + public static readonly PerspexProperty ColumnSpanProperty = + PerspexProperty.RegisterAttached("ColumnSpan", 1); + + public static readonly PerspexProperty IsSharedSizeScopeProperty = + PerspexProperty.RegisterAttached("IsSharedSizeScopeProperty"); + + public static readonly PerspexProperty RowProperty = + PerspexProperty.RegisterAttached("Row"); + + public static readonly PerspexProperty RowSpanProperty = + PerspexProperty.RegisterAttached("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 sizes = new List(); + 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; } + } + } +} diff --git a/Perspex.Controls/GridLength.cs b/Perspex.Controls/GridLength.cs new file mode 100644 index 0000000000..5958b58898 --- /dev/null +++ b/Perspex.Controls/GridLength.cs @@ -0,0 +1,121 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + + public enum GridUnitType + { + Auto = 0, + Pixel = 1, + Star = 2, + } + + public struct GridLength : IEquatable + { + 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; + } + } +} diff --git a/Perspex.Controls/HeaderedContentControl.cs b/Perspex.Controls/HeaderedContentControl.cs new file mode 100644 index 0000000000..5afa057ee5 --- /dev/null +++ b/Perspex.Controls/HeaderedContentControl.cs @@ -0,0 +1,24 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections.Generic; + using System.Linq; + + public class HeaderedContentControl : ContentControl, IHeadered + { + public static readonly PerspexProperty HeaderProperty = + PerspexProperty.Register("Header"); + + public object Header + { + get { return this.GetValue(HeaderProperty); } + set { this.SetValue(HeaderProperty, value); } + } + } +} diff --git a/Perspex.Controls/HeaderedItemsControl.cs b/Perspex.Controls/HeaderedItemsControl.cs new file mode 100644 index 0000000000..d65d1635b4 --- /dev/null +++ b/Perspex.Controls/HeaderedItemsControl.cs @@ -0,0 +1,25 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections; + using System.Linq; + using System.Reactive.Linq; + + public class HeaderedItemsControl : ItemsControl + { + public static readonly PerspexProperty HeaderProperty = + HeaderedContentControl.HeaderProperty.AddOwner(); + + public object Header + { + get { return this.GetValue(HeaderProperty); } + set { this.SetValue(HeaderProperty, value); } + } + } +} diff --git a/Perspex.Controls/IGlobalDataTemplates.cs b/Perspex.Controls/IGlobalDataTemplates.cs new file mode 100644 index 0000000000..e27b8f6f18 --- /dev/null +++ b/Perspex.Controls/IGlobalDataTemplates.cs @@ -0,0 +1,14 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System.Collections.Generic; + + public interface IGlobalDataTemplates : IEnumerable + { + } +} diff --git a/Perspex.Controls/IHeadered.cs b/Perspex.Controls/IHeadered.cs new file mode 100644 index 0000000000..b7ed08002a --- /dev/null +++ b/Perspex.Controls/IHeadered.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public interface IHeadered + { + object Header { get; set; } + } +} diff --git a/Perspex.Controls/Image.cs b/Perspex.Controls/Image.cs new file mode 100644 index 0000000000..cb594b3340 --- /dev/null +++ b/Perspex.Controls/Image.cs @@ -0,0 +1,115 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using Perspex.Media; + using Perspex.Media.Imaging; + + public class Image : Control + { + public static readonly PerspexProperty SourceProperty = + PerspexProperty.Register("Source"); + + public static readonly PerspexProperty StretchProperty = + PerspexProperty.Register("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; + } + } +} diff --git a/Perspex.Controls/ItemsControl.cs b/Perspex.Controls/ItemsControl.cs new file mode 100644 index 0000000000..7b26728be9 --- /dev/null +++ b/Perspex.Controls/ItemsControl.cs @@ -0,0 +1,137 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 ItemsProperty = + PerspexProperty.Register("Items"); + + public static readonly PerspexProperty ItemsPanelProperty = + PerspexProperty.Register("ItemsPanel", defaultValue: DefaultPanel); + + private Dictionary controlsByItem = new Dictionary(); + + private Dictionary itemsByControl = new Dictionary(); + + 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 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 value) + { + INotifyPropertyChanged inpc = value.Item1 as INotifyPropertyChanged; + + if (inpc != null) + { + inpc.PropertyChanged -= ItemsPropertyChanged; + } + + if (value.Item2 == null || !value.Item2.OfType().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"); + } + } + } + } +} diff --git a/Perspex.Controls/ItemsPanelTemplate.cs b/Perspex.Controls/ItemsPanelTemplate.cs new file mode 100644 index 0000000000..1e2375c5b0 --- /dev/null +++ b/Perspex.Controls/ItemsPanelTemplate.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Controls +{ + public class ItemsPanelTemplate + { + public ItemsPanelTemplate(Func build) + { + Contract.Requires(build != null); + + this.Build = build; + } + + public Func Build { get; private set; } + } +} diff --git a/Perspex.Controls/ItemsPresenter.cs b/Perspex.Controls/ItemsPresenter.cs new file mode 100644 index 0000000000..6d5a4d4e40 --- /dev/null +++ b/Perspex.Controls/ItemsPresenter.cs @@ -0,0 +1,178 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 ItemsProperty = + ItemsControl.ItemsProperty.AddOwner(); + + public static readonly PerspexProperty ItemsPanelProperty = + ItemsControl.ItemsPanelProperty.AddOwner(); + + 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 CreateItemControls(IEnumerable items) + { + if (items != null) + { + return items + .Cast() + .Select(x => this.CreateItemControl(x)) + .OfType(); + } + else + { + return Enumerable.Empty(); + } + } + + 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 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; + } + } + } + } +} diff --git a/Perspex.Controls/Panel.cs b/Perspex.Controls/Panel.cs new file mode 100644 index 0000000000..56bb01e896 --- /dev/null +++ b/Perspex.Controls/Panel.cs @@ -0,0 +1,45 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Base class for controls that can contain multiple children. + /// + 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(value != null); + + if (this.children != value) + { + this.children = value; + this.ClearVisualChildren(); + this.AddVisualChildren(value); + } + } + } + } +} diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj new file mode 100644 index 0000000000..0d7099fe4a --- /dev/null +++ b/Perspex.Controls/Perspex.Controls.csproj @@ -0,0 +1,138 @@ + + + + + 11.0 + Debug + AnyCPU + {D2221C82-4A25-4583-9B43-D791E3F6820C} + Library + Properties + Perspex.Controls + Perspex.Controls + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll + + + ..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll + + + ..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll + + + ..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll + + + + + + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + + + + + 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}. + + + + + \ No newline at end of file diff --git a/Perspex.Controls/Properties/AssemblyInfo.cs b/Perspex.Controls/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..e5f678d5a7 --- /dev/null +++ b/Perspex.Controls/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Controls")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Controls")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Perspex.Controls/RowDefinition.cs b/Perspex.Controls/RowDefinition.cs new file mode 100644 index 0000000000..230fb7a14a --- /dev/null +++ b/Perspex.Controls/RowDefinition.cs @@ -0,0 +1,53 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public class RowDefinition : DefinitionBase + { + public static readonly PerspexProperty MaxHeightProperty = + PerspexProperty.Register("MaxHeight", double.PositiveInfinity); + + public static readonly PerspexProperty MinHeightProperty = + PerspexProperty.Register("MinHeight"); + + public static readonly PerspexProperty HeightProperty = + PerspexProperty.Register("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); } + } + } +} diff --git a/Perspex.Controls/RowDefinitions.cs b/Perspex.Controls/RowDefinitions.cs new file mode 100644 index 0000000000..60ea0da556 --- /dev/null +++ b/Perspex.Controls/RowDefinitions.cs @@ -0,0 +1,12 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public class RowDefinitions : PerspexList + { + } +} diff --git a/Perspex.Controls/SelectingItemsControl.cs b/Perspex.Controls/SelectingItemsControl.cs new file mode 100644 index 0000000000..28ba4ff363 --- /dev/null +++ b/Perspex.Controls/SelectingItemsControl.cs @@ -0,0 +1,20 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public class SelectingItemsControl : ItemsControl + { + public static readonly PerspexProperty SelectedItemProperty = + PerspexProperty.Register("SelectedItem"); + + public object SelectedItem + { + get { return this.GetValue(SelectedItemProperty); } + set { this.SetValue(SelectedItemProperty, value); } + } + } +} diff --git a/Perspex.Controls/StackPanel.cs b/Perspex.Controls/StackPanel.cs new file mode 100644 index 0000000000..4ce3d9a9ac --- /dev/null +++ b/Perspex.Controls/StackPanel.cs @@ -0,0 +1,155 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 GapProperty = + PerspexProperty.Register("Gap"); + + public static readonly PerspexProperty OrientationProperty = + PerspexProperty.Register("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); + } + } +} diff --git a/Perspex.Controls/TabControl.cs b/Perspex.Controls/TabControl.cs new file mode 100644 index 0000000000..cdd30d6186 --- /dev/null +++ b/Perspex.Controls/TabControl.cs @@ -0,0 +1,62 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reactive.Linq; + + public class TabControl : SelectingItemsControl + { + public static readonly PerspexProperty SelectedContentProperty = + PerspexProperty.Register("SelectedContent"); + + private TabStrip tabStrip; + + public TabControl() + { + this.GetObservable(SelectedItemProperty).Skip(1).Subscribe(this.SelectedItemChanged); + } + + protected override void OnTemplateApplied() + { + this.tabStrip = this.GetTemplateControls() + .OfType() + .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); + } + } + } +} diff --git a/Perspex.Controls/TabItem.cs b/Perspex.Controls/TabItem.cs new file mode 100644 index 0000000000..351f4f2941 --- /dev/null +++ b/Perspex.Controls/TabItem.cs @@ -0,0 +1,26 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public class TabItem : HeaderedContentControl + { + public static readonly PerspexProperty IsSelectedProperty = + PerspexProperty.Register("IsSelected"); + + public TabItem() + { + this.AddPseudoClass(IsSelectedProperty, ":selected"); + AffectsRender(IsSelectedProperty); + } + + public bool IsSelected + { + get { return this.GetValue(IsSelectedProperty); } + set { this.SetValue(IsSelectedProperty, value); } + } + } +} diff --git a/Perspex.Controls/TabStrip.cs b/Perspex.Controls/TabStrip.cs new file mode 100644 index 0000000000..4935f4114f --- /dev/null +++ b/Perspex.Controls/TabStrip.cs @@ -0,0 +1,88 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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().FirstOrDefault(x => x.IsSelected) ?? + items.OfType().FirstOrDefault(); + } + else + { + this.SelectedItem = null; + } + } + + private void OnPointerPressed(object sender, PointerEventArgs e) + { + IVisual source = (IVisual)e.Source; + ContentPresenter presenter = source.GetVisualAncestors() + .OfType() + .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; + } + } + } +} diff --git a/Perspex.Controls/TemplatedControl.cs b/Perspex.Controls/TemplatedControl.cs new file mode 100644 index 0000000000..bc88d5fc49 --- /dev/null +++ b/Perspex.Controls/TemplatedControl.cs @@ -0,0 +1,74 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 TemplateProperty = + PerspexProperty.Register("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 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() + { + } + } +} diff --git a/Perspex.Controls/TextBlock.cs b/Perspex.Controls/TextBlock.cs new file mode 100644 index 0000000000..84b3ac0dcd --- /dev/null +++ b/Perspex.Controls/TextBlock.cs @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using Perspex.Media; + using Perspex.Platform; + using Splat; + + public class TextBlock : Control + { + public static readonly PerspexProperty TextProperty = + PerspexProperty.Register("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(); + return textService.Measure(this.FormattedText, availableSize); + } + + return new Size(); + } + } +} diff --git a/Perspex.Controls/TextBox.cs b/Perspex.Controls/TextBox.cs new file mode 100644 index 0000000000..adf102fdd2 --- /dev/null +++ b/Perspex.Controls/TextBox.cs @@ -0,0 +1,133 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 TextProperty = + TextBlock.TextProperty.AddOwner(); + + 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() + .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(); + this.CaretIndex = platform.TextService.GetCaretIndex( + this.textBoxView.FormattedText, + e.GetPosition(this.textBoxView), + this.ActualSize); + } + } +} diff --git a/Perspex.Controls/TextBoxView.cs b/Perspex.Controls/TextBoxView.cs new file mode 100644 index 0000000000..0f02f94838 --- /dev/null +++ b/Perspex.Controls/TextBoxView.cs @@ -0,0 +1,125 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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(); + 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(); + 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(); + } + } +} diff --git a/Perspex.Controls/ToggleButton.cs b/Perspex.Controls/ToggleButton.cs new file mode 100644 index 0000000000..288b826a30 --- /dev/null +++ b/Perspex.Controls/ToggleButton.cs @@ -0,0 +1,28 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + + public class ToggleButton : Button + { + public static readonly PerspexProperty IsCheckedProperty = + PerspexProperty.Register("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); } + } + } +} diff --git a/Perspex.Controls/TreeDataTemplate.cs b/Perspex.Controls/TreeDataTemplate.cs new file mode 100644 index 0000000000..5a78fea93f --- /dev/null +++ b/Perspex.Controls/TreeDataTemplate.cs @@ -0,0 +1,107 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections; + + public class TreeDataTemplate : DataTemplate + { + public TreeDataTemplate( + Func build, + Func itemsSelector) + : this(o => true, build, itemsSelector) + { + } + + public TreeDataTemplate( + Type type, + Func build, + Func itemsSelector) + : this(o => IsInstance(o, type), build, itemsSelector) + { + } + + public TreeDataTemplate( + Type type, + Func build, + Func itemsSelector, + Func isExpanded) + : this(o => IsInstance(o, type), build, itemsSelector, isExpanded) + { + } + + public TreeDataTemplate( + Func match, + Func build, + Func itemsSelector) + : this(match, build, itemsSelector, _ => false) + { + this.ItemsSelector = itemsSelector; + } + + public TreeDataTemplate( + Func match, + Func build, + Func itemsSelector, + Func isExpanded) + : base(match, build) + { + this.ItemsSelector = itemsSelector; + this.IsExpanded = isExpanded; + } + + public Func ItemsSelector { get; private set; } + + public Func IsExpanded { get; private set; } + } + + public class TreeDataTemplate : TreeDataTemplate + { + public TreeDataTemplate( + Func build, + Func itemsSelector) + : base(typeof(T), Cast(build), Cast(itemsSelector)) + { + } + + public TreeDataTemplate( + Func build, + Func itemsSelector, + Func isExpanded) + : base(typeof(T), Cast(build), Cast(itemsSelector), Cast(isExpanded)) + { + } + + public TreeDataTemplate( + Func match, + Func build, + Func itemsSelector) + : base(CastMatch(match), Cast(build), Cast(itemsSelector)) + { + } + + public TreeDataTemplate( + Func match, + Func build, + Func itemsSelector, + Func isExpanded) + : base(CastMatch(match), Cast(build), Cast(itemsSelector), Cast(isExpanded)) + { + } + + private static Func CastMatch(Func f) + { + return o => (o is T) ? f((T)o) : false; + } + + private static Func Cast(Func f) + { + return o => f((T)o); + } + } +} diff --git a/Perspex.Controls/TreeView.cs b/Perspex.Controls/TreeView.cs new file mode 100644 index 0000000000..9f2ad51838 --- /dev/null +++ b/Perspex.Controls/TreeView.cs @@ -0,0 +1,78 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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() + .FirstOrDefault(); + + if (contentPresenter != null) + { + TreeViewItem item = contentPresenter.TemplatedParent as TreeViewItem; + + if (item != null) + { + foreach (var i in this.GetVisualDescendents().OfType()) + { + i.IsSelected = i == item; + } + + this.SelectedItem = this.GetItemForControl(item); + } + } + + } + } +} diff --git a/Perspex.Controls/TreeViewItem.cs b/Perspex.Controls/TreeViewItem.cs new file mode 100644 index 0000000000..b3b3826c31 --- /dev/null +++ b/Perspex.Controls/TreeViewItem.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System.Linq; + + public class TreeViewItem : HeaderedItemsControl + { + public static readonly PerspexProperty IsExpandedProperty = + PerspexProperty.Register("IsExpanded"); + + public static readonly PerspexProperty IsSelectedProperty = + PerspexProperty.Register("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().FirstOrDefault(); + } + } +} diff --git a/Perspex.Controls/packages.config b/Perspex.Controls/packages.config new file mode 100644 index 0000000000..af8b5868bf --- /dev/null +++ b/Perspex.Controls/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj b/Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj index 45c29aa863..623f1bd230 100644 --- a/Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj +++ b/Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj @@ -38,9 +38,9 @@ ..\packages\Magick.NET-Q16-x86.6.8.9.101\lib\net40-client\Magick.NET-x86.dll - + False - ..\packages\Splat.1.1.1\lib\Net45\Splat.dll + ..\packages\Splat.1.3.3\lib\Net45\Splat.dll diff --git a/Perspex.Direct2D1.RenderTests/packages.config b/Perspex.Direct2D1.RenderTests/packages.config index 3b12d4494d..2693a06d52 100644 --- a/Perspex.Direct2D1.RenderTests/packages.config +++ b/Perspex.Direct2D1.RenderTests/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/Perspex.Direct2D1/Perspex.Direct2D1.csproj b/Perspex.Direct2D1/Perspex.Direct2D1.csproj index 0d4b96dded..36fce33f31 100644 --- a/Perspex.Direct2D1/Perspex.Direct2D1.csproj +++ b/Perspex.Direct2D1/Perspex.Direct2D1.csproj @@ -39,8 +39,9 @@ ..\packages\SharpDX.DXGI.2.5.0\lib\net40\SharpDX.DXGI.dll - - ..\packages\Splat.1.1.1\lib\Net45\Splat.dll + + False + ..\packages\Splat.1.3.3\lib\Net45\Splat.dll diff --git a/Perspex.Direct2D1/packages.config b/Perspex.Direct2D1/packages.config index fd261d2b22..f1ba5a0459 100644 --- a/Perspex.Direct2D1/packages.config +++ b/Perspex.Direct2D1/packages.config @@ -6,5 +6,5 @@ - + \ No newline at end of file diff --git a/Perspex.Input/FocusManager.cs b/Perspex.Input/FocusManager.cs new file mode 100644 index 0000000000..bf59e85bfd --- /dev/null +++ b/Perspex.Input/FocusManager.cs @@ -0,0 +1,47 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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, + }); + } + } + } +} diff --git a/Perspex.Input/IFocusManager.cs b/Perspex.Input/IFocusManager.cs new file mode 100644 index 0000000000..3399a950dd --- /dev/null +++ b/Perspex.Input/IFocusManager.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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); + } +} diff --git a/Perspex.Input/IInputDevice.cs b/Perspex.Input/IInputDevice.cs new file mode 100644 index 0000000000..e2ba98a205 --- /dev/null +++ b/Perspex.Input/IInputDevice.cs @@ -0,0 +1,12 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + public interface IInputDevice + { + } +} diff --git a/Perspex.Input/IInputElement.cs b/Perspex.Input/IInputElement.cs new file mode 100644 index 0000000000..f7e3c28133 --- /dev/null +++ b/Perspex.Input/IInputElement.cs @@ -0,0 +1,36 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + using System; + using Perspex.Interactivity; + + public interface IInputElement : IInteractive + { + event EventHandler GotFocus; + + event EventHandler LostFocus; + + event EventHandler KeyDown; + + event EventHandler PointerEnter; + + event EventHandler PointerLeave; + + event EventHandler PointerPressed; + + event EventHandler PointerReleased; + + bool Focusable { get; } + + bool IsFocused { get; } + + bool IsPointerOver { get; } + + void Focus(); + } +} diff --git a/Perspex.Input/IInputManager.cs b/Perspex.Input/IInputManager.cs new file mode 100644 index 0000000000..0cddac4e33 --- /dev/null +++ b/Perspex.Input/IInputManager.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + using System; + using Perspex.Input.Raw; + using Perspex.Layout; + + public interface IInputManager + { + IObservable RawEventReceived { get; } + + void Process(RawInputEventArgs e); + + void SetPointerOver(IPointerDevice device, IVisual visual, Point p); + } +} diff --git a/Perspex.Input/IKeyboardDevice.cs b/Perspex.Input/IKeyboardDevice.cs new file mode 100644 index 0000000000..53e708a26e --- /dev/null +++ b/Perspex.Input/IKeyboardDevice.cs @@ -0,0 +1,33 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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; } + } +} diff --git a/Perspex.Input/IMouseDevice.cs b/Perspex.Input/IMouseDevice.cs new file mode 100644 index 0000000000..b4b30ee65b --- /dev/null +++ b/Perspex.Input/IMouseDevice.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + public interface IMouseDevice : IPointerDevice + { + Point Position { get; } + } +} diff --git a/Perspex.Input/IPointerDevice.cs b/Perspex.Input/IPointerDevice.cs new file mode 100644 index 0000000000..a130814e41 --- /dev/null +++ b/Perspex.Input/IPointerDevice.cs @@ -0,0 +1,20 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + using System; + using Perspex.Interactivity; + + public interface IPointerDevice : IInputDevice + { + IInteractive Captured { get; } + + void Capture(IInteractive control); + + Point GetPosition(IVisual relativeTo); + } +} diff --git a/Perspex.Input/InputElement.cs b/Perspex.Input/InputElement.cs new file mode 100644 index 0000000000..34a6d36b1d --- /dev/null +++ b/Perspex.Input/InputElement.cs @@ -0,0 +1,121 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + using System; + using Perspex.Interactivity; + using Splat; + + public class InputElement : Interactive, IInputElement + { + public static readonly PerspexProperty FocusableProperty = + PerspexProperty.Register("Focusable"); + + public static readonly PerspexProperty IsFocusedProperty = + PerspexProperty.Register("IsFocused", false); + + public static readonly PerspexProperty IsPointerOverProperty = + PerspexProperty.Register("IsPointerOver"); + + public static readonly RoutedEvent GotFocusEvent = + RoutedEvent.Register("GotFocus", RoutingStrategy.Bubble); + + public static readonly RoutedEvent LostFocusEvent = + RoutedEvent.Register("LostFocus", RoutingStrategy.Bubble); + + public static readonly RoutedEvent KeyDownEvent = + RoutedEvent.Register("KeyDown", RoutingStrategy.Bubble); + + public static readonly RoutedEvent PreviewKeyDownEvent = + RoutedEvent.Register("PreviewKeyDown", RoutingStrategy.Tunnel); + + public static readonly RoutedEvent PointerEnterEvent = + RoutedEvent.Register("PointerEnter", RoutingStrategy.Direct); + + public static readonly RoutedEvent PointerLeaveEvent = + RoutedEvent.Register("PointerLeave", RoutingStrategy.Direct); + + public static readonly RoutedEvent PointerPressedEvent = + RoutedEvent.Register("PointerPressed", RoutingStrategy.Bubble); + + public static readonly RoutedEvent PointerReleasedEvent = + RoutedEvent.Register("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 GotFocus + { + add { this.AddHandler(GotFocusEvent, value); } + remove { this.RemoveHandler(GotFocusEvent, value); } + } + + public event EventHandler LostFocus + { + add { this.AddHandler(LostFocusEvent, value); } + remove { this.RemoveHandler(LostFocusEvent, value); } + } + + public event EventHandler KeyDown + { + add { this.AddHandler(KeyDownEvent, value); } + remove { this.RemoveHandler(KeyDownEvent, value); } + } + + public event EventHandler PointerEnter + { + add { this.AddHandler(PointerEnterEvent, value); } + remove { this.RemoveHandler(PointerEnterEvent, value); } + } + + public event EventHandler PointerLeave + { + add { this.AddHandler(PointerLeaveEvent, value); } + remove { this.RemoveHandler(PointerLeaveEvent, value); } + } + + public event EventHandler PointerPressed + { + add { this.AddHandler(PointerPressedEvent, value); } + remove { this.RemoveHandler(PointerPressedEvent, value); } + } + + public event EventHandler 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().Focus(this); + } + } +} diff --git a/Perspex.Input/InputManager.cs b/Perspex.Input/InputManager.cs new file mode 100644 index 0000000000..c0ea7aea18 --- /dev/null +++ b/Perspex.Input/InputManager.cs @@ -0,0 +1,64 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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 pointerOvers = new List(); + + private Subject rawEventReceived = new Subject(); + + public IObservable RawEventReceived + { + get { return this.rawEventReceived; } + } + + public void Process(RawInputEventArgs e) + { + this.rawEventReceived.OnNext(e); + } + + public void SetPointerOver(IPointerDevice device, IVisual visual, Point p) + { + IEnumerable hits = visual.GetVisualsAt(p).OfType(); + + 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); + } + } + } +} diff --git a/Perspex.Input/Key.cs b/Perspex.Input/Key.cs new file mode 100644 index 0000000000..b966f9223f --- /dev/null +++ b/Perspex.Input/Key.cs @@ -0,0 +1,214 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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, + } +} diff --git a/Perspex.Input/KeyEventArgs.cs b/Perspex.Input/KeyEventArgs.cs new file mode 100644 index 0000000000..b8dcd8df4c --- /dev/null +++ b/Perspex.Input/KeyEventArgs.cs @@ -0,0 +1,20 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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; } + } +} diff --git a/Perspex.Input/KeyboardDevice.cs b/Perspex.Input/KeyboardDevice.cs new file mode 100644 index 0000000000..d30afaa2a5 --- /dev/null +++ b/Perspex.Input/KeyboardDevice.cs @@ -0,0 +1,75 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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() + .Where(x => x.Device == this) + .Subscribe(this.ProcessRawEvent); + } + + public IInputManager InputManager + { + get { return Locator.Current.GetService(); } + } + + public IFocusManager FocusManager + { + get { return Locator.Current.GetService(); } + } + + 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; + } + } + } + } +} diff --git a/Perspex.Input/MouseDevice.cs b/Perspex.Input/MouseDevice.cs new file mode 100644 index 0000000000..89fa0cb8cd --- /dev/null +++ b/Perspex.Input/MouseDevice.cs @@ -0,0 +1,163 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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() + .Where(x => x.Device == this) + .Subscribe(this.ProcessRawEvent); + } + + public IInteractive Captured + { + get; + protected set; + } + + public IInputManager InputManager + { + get { return Locator.Current.GetService(); } + } + + 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().FirstOrDefault(x => x.Focusable); + } + + private IInteractive GetSource(IVisual hit) + { + return this.Captured ?? + (hit as IInteractive) ?? + hit.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + } + } +} diff --git a/Perspex.Input/Perspex.Input.csproj b/Perspex.Input/Perspex.Input.csproj new file mode 100644 index 0000000000..83d5d30154 --- /dev/null +++ b/Perspex.Input/Perspex.Input.csproj @@ -0,0 +1,108 @@ + + + + + 11.0 + Debug + AnyCPU + {62024B2D-53EB-4638-B26B-85EEAA54866E} + Library + Properties + Perspex.Input + Perspex.Input + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + + + + + + + + + + + + + + + + + + + + + + + + ..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll + + + ..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll + + + ..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll + + + ..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll + + + + + + + + + + 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}. + + + + + \ No newline at end of file diff --git a/Perspex.Input/PointerEventArgs.cs b/Perspex.Input/PointerEventArgs.cs new file mode 100644 index 0000000000..efabc66073 --- /dev/null +++ b/Perspex.Input/PointerEventArgs.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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); + } + } +} diff --git a/Perspex.Input/Properties/AssemblyInfo.cs b/Perspex.Input/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..cd5f1f96e8 --- /dev/null +++ b/Perspex.Input/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Input")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Input")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Perspex.Input/Raw/RawInputEventArgs.cs b/Perspex.Input/Raw/RawInputEventArgs.cs new file mode 100644 index 0000000000..0e9a08c6fe --- /dev/null +++ b/Perspex.Input/Raw/RawInputEventArgs.cs @@ -0,0 +1,27 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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(device != null); + + this.Device = device; + } + + public IInputDevice Device { get; private set; } + } +} diff --git a/Perspex.Input/Raw/RawKeyEventArgs.cs b/Perspex.Input/Raw/RawKeyEventArgs.cs new file mode 100644 index 0000000000..887030b375 --- /dev/null +++ b/Perspex.Input/Raw/RawKeyEventArgs.cs @@ -0,0 +1,31 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 Tricycle. All rights reserved. +// +// ----------------------------------------------------------------------- + +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; } + } +} diff --git a/Perspex.Input/Raw/RawMouseEventArgs.cs b/Perspex.Input/Raw/RawMouseEventArgs.cs new file mode 100644 index 0000000000..41da9ec88a --- /dev/null +++ b/Perspex.Input/Raw/RawMouseEventArgs.cs @@ -0,0 +1,42 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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(device != null); + Contract.Requires(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; } + } +} diff --git a/Perspex.Input/packages.config b/Perspex.Input/packages.config new file mode 100644 index 0000000000..af8b5868bf --- /dev/null +++ b/Perspex.Input/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Perspex.Interactivity/IInteractive.cs b/Perspex.Interactivity/IInteractive.cs new file mode 100644 index 0000000000..d1b2d7a8b3 --- /dev/null +++ b/Perspex.Interactivity/IInteractive.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Interactivity +{ + public interface IInteractive : IVisual + { + void RaiseEvent(RoutedEventArgs e); + } +} diff --git a/Perspex.Interactivity/Interactive.cs b/Perspex.Interactivity/Interactive.cs new file mode 100644 index 0000000000..2815777dd9 --- /dev/null +++ b/Perspex.Interactivity/Interactive.cs @@ -0,0 +1,124 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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> eventHandlers = new Dictionary>(); + + public void AddHandler(RoutedEvent routedEvent, Delegate handler) + { + Contract.Requires(routedEvent != null); + Contract.Requires(handler != null); + + List delegates; + + if (!this.eventHandlers.TryGetValue(routedEvent, out delegates)) + { + delegates = new List(); + this.eventHandlers.Add(routedEvent, delegates); + } + + delegates.Add(handler); + } + + public IObservable> GetObservable(RoutedEvent routedEvent) where T : RoutedEventArgs + { + Contract.Requires(routedEvent != null); + + return Observable.FromEventPattern( + handler => this.AddHandler(routedEvent, handler), + handler => this.RemoveHandler(routedEvent, handler)); + } + + public void RemoveHandler(RoutedEvent routedEvent, Delegate handler) + { + Contract.Requires(routedEvent != null); + Contract.Requires(handler != null); + + List delegates; + + if (this.eventHandlers.TryGetValue(routedEvent, out delegates)) + { + delegates.Remove(handler); + } + } + + public void RaiseEvent(RoutedEventArgs e) + { + Contract.Requires(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(e != null); + + foreach (var target in this.GetSelfAndVisualAncestors().OfType()) + { + target.RaiseEventImpl(e); + + if (e.Handled) + { + break; + } + } + } + + private void TunnelEvent(RoutedEventArgs e) + { + Contract.Requires(e != null); + + foreach (var target in this.GetSelfAndVisualAncestors().OfType().Reverse()) + { + target.RaiseEventImpl(e); + + if (e.Handled) + { + break; + } + } + } + + private void RaiseEventImpl(RoutedEventArgs e) + { + Contract.Requires(e != null); + + List delegates; + + if (this.eventHandlers.TryGetValue(e.RoutedEvent, out delegates)) + { + foreach (Delegate handler in delegates) + { + handler.DynamicInvoke(this, e); + } + } + } + } +} diff --git a/Perspex.Interactivity/Perspex.Interactivity.csproj b/Perspex.Interactivity/Perspex.Interactivity.csproj new file mode 100644 index 0000000000..73c33e8f72 --- /dev/null +++ b/Perspex.Interactivity/Perspex.Interactivity.csproj @@ -0,0 +1,90 @@ + + + + + 11.0 + Debug + AnyCPU + {6B0ED19D-A08B-461C-A9D9-A9EE40B0C06B} + Library + Properties + Perspex + Perspex.Interactive + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + + + + + + + + + + ..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll + + + ..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll + + + ..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll + + + ..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll + + + + + + + + + + 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}. + + + + + \ No newline at end of file diff --git a/Perspex.Interactivity/Properties/AssemblyInfo.cs b/Perspex.Interactivity/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..52b2807242 --- /dev/null +++ b/Perspex.Interactivity/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Interactive")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Interactive")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Perspex.Interactivity/RoutedEvent.cs b/Perspex.Interactivity/RoutedEvent.cs new file mode 100644 index 0000000000..bec71e331d --- /dev/null +++ b/Perspex.Interactivity/RoutedEvent.cs @@ -0,0 +1,97 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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(name != null); + Contract.Requires(eventArgsType != null); + Contract.Requires(ownerType != null); + Contract.Requires(typeof(RoutedEventArgs).GetTypeInfo().IsAssignableFrom(eventArgsType.GetTypeInfo())); + Contract.Requires(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 Register( + string name, + RoutingStrategy routingStrategy) + where TOwner : Interactive + where TEventArgs : RoutedEventArgs + { + Contract.Requires(name != null); + + return new RoutedEvent(name, routingStrategy, typeof(TOwner)); + } + + public static RoutedEvent Register( + string name, + RoutingStrategy routingStrategy, + Type ownerType) + where TEventArgs : RoutedEventArgs + { + Contract.Requires(name != null); + + return new RoutedEvent(name, routingStrategy, ownerType); + } + } + + public class RoutedEvent : RoutedEvent + where TEventArgs : RoutedEventArgs + { + public RoutedEvent(string name, RoutingStrategy routingStrategy, Type ownerType) + : base(name, routingStrategy, typeof(TEventArgs), ownerType) + { + Contract.Requires(name != null); + Contract.Requires(ownerType != null); + Contract.Requires(typeof(Interactive).GetTypeInfo().IsAssignableFrom(ownerType.GetTypeInfo())); + } + } +} diff --git a/Perspex.Interactivity/RoutedEventArgs.cs b/Perspex.Interactivity/RoutedEventArgs.cs new file mode 100644 index 0000000000..23d97b9818 --- /dev/null +++ b/Perspex.Interactivity/RoutedEventArgs.cs @@ -0,0 +1,36 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +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; } + } +} diff --git a/Perspex.Interactivity/packages.config b/Perspex.Interactivity/packages.config new file mode 100644 index 0000000000..af8b5868bf --- /dev/null +++ b/Perspex.Interactivity/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Perspex.Layout/ILayoutManager.cs b/Perspex.Layout/ILayoutManager.cs new file mode 100644 index 0000000000..04af09578b --- /dev/null +++ b/Perspex.Layout/ILayoutManager.cs @@ -0,0 +1,22 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Layout +{ + using System; + using System.Reactive; + + public interface ILayoutManager + { + IObservable LayoutNeeded { get; } + + void ExecuteLayoutPass(); + + void InvalidateMeasure(ILayoutable item); + + void InvalidateArrange(ILayoutable item); + } +} diff --git a/Perspex.Layout/ILayoutRoot.cs b/Perspex.Layout/ILayoutRoot.cs new file mode 100644 index 0000000000..4962760a21 --- /dev/null +++ b/Perspex.Layout/ILayoutRoot.cs @@ -0,0 +1,15 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Layout +{ + public interface ILayoutRoot : ILayoutable + { + Size ClientSize { get; } + + ILayoutManager LayoutManager { get; } + } +} diff --git a/Perspex.Layout/ILayoutable.cs b/Perspex.Layout/ILayoutable.cs new file mode 100644 index 0000000000..04f95652e8 --- /dev/null +++ b/Perspex.Layout/ILayoutable.cs @@ -0,0 +1,37 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Layout +{ + public interface ILayoutable : IVisual + { + Size? DesiredSize { get; } + + double Width { get; } + + double Height { get; } + + double MinWidth { get; } + + double MaxWidth { get; } + + double MinHeight { get; } + + double MaxHeight { get; } + + HorizontalAlignment HorizontalAlignment { get; } + + VerticalAlignment VerticalAlignment { get; } + + void Arrange(Rect rect); + + void Measure(Size availableSize); + + void InvalidateArrange(); + + void InvalidateMeasure(); + } +} diff --git a/Perspex.Layout/LayoutHelper.cs b/Perspex.Layout/LayoutHelper.cs new file mode 100644 index 0000000000..6708803812 --- /dev/null +++ b/Perspex.Layout/LayoutHelper.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Layout +{ + using System; + + public static class LayoutHelper + { + public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints) + { + double width = (control.Width > 0) ? control.Width : constraints.Width; + double height = (control.Height > 0) ? control.Height : constraints.Height; + width = Math.Min(width, control.MaxWidth); + width = Math.Max(width, control.MinWidth); + height = Math.Min(height, control.MaxHeight); + height = Math.Max(height, control.MinHeight); + return new Size(width, height); + } + + public static Size MeasureDecorator( + ILayoutable decorator, + ILayoutable content, + Size availableSize, + Thickness padding) + { + double width = 0; + double height = 0; + + if (content != null) + { + content.Measure(availableSize.Deflate(padding)); + Size s = content.DesiredSize.Value.Inflate(padding); + width = s.Width; + height = s.Height; + } + + if (decorator.Width > 0) + { + width = decorator.Width; + } + + if (decorator.Height > 0) + { + height = decorator.Height; + } + + return new Size(width, height); + } + } +} diff --git a/Perspex.Layout/LayoutManager.cs b/Perspex.Layout/LayoutManager.cs new file mode 100644 index 0000000000..559006ad82 --- /dev/null +++ b/Perspex.Layout/LayoutManager.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Layout +{ + using System; + using System.Linq; + using System.Reactive; + using System.Reactive.Subjects; + + public class LayoutManager : ILayoutManager + { + private ILayoutRoot root; + + private Subject layoutNeeded; + + public LayoutManager(ILayoutRoot root) + { + Contract.Requires(root != null); + + this.root = root; + this.layoutNeeded = new Subject(); + } + + public IObservable LayoutNeeded + { + get { return this.layoutNeeded; } + } + + public void ExecuteLayoutPass() + { + if (this.root != null) + { + this.root.Measure(this.root.ClientSize); + this.root.Arrange(new Rect(this.root.ClientSize)); + } + + this.root = null; + } + + public void InvalidateMeasure(ILayoutable item) + { + IVisual visual = item as IVisual; + this.layoutNeeded.OnNext(Unit.Default); + } + + public void InvalidateArrange(ILayoutable item) + { + IVisual visual = item as IVisual; + this.layoutNeeded.OnNext(Unit.Default); + } + } +} diff --git a/Perspex.Layout/Layoutable.cs b/Perspex.Layout/Layoutable.cs new file mode 100644 index 0000000000..8dd8a3f4a0 --- /dev/null +++ b/Perspex.Layout/Layoutable.cs @@ -0,0 +1,277 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Layout +{ + using System; + using System.Linq; + + public enum HorizontalAlignment + { + Stretch, + Left, + Center, + Right, + } + + public enum VerticalAlignment + { + Stretch, + Top, + Center, + Bottom, + } + + public class Layoutable : Visual, ILayoutable + { + public static readonly PerspexProperty WidthProperty = + PerspexProperty.Register("Width", double.NaN); + + public static readonly PerspexProperty HeightProperty = + PerspexProperty.Register("Height", double.NaN); + + public static readonly PerspexProperty MinWidthProperty = + PerspexProperty.Register("MinWidth"); + + public static readonly PerspexProperty MaxWidthProperty = + PerspexProperty.Register("MaxWidth", double.PositiveInfinity); + + public static readonly PerspexProperty MinHeightProperty = + PerspexProperty.Register("MinHeight"); + + public static readonly PerspexProperty MaxHeightProperty = + PerspexProperty.Register("MaxHeight", double.PositiveInfinity); + + public static readonly PerspexProperty MarginProperty = + PerspexProperty.Register("Margin"); + + public static readonly PerspexProperty HorizontalAlignmentProperty = + PerspexProperty.Register("HorizontalAlignment"); + + public static readonly PerspexProperty VerticalAlignmentProperty = + PerspexProperty.Register("VerticalAlignment"); + + public double Width + { + get { return this.GetValue(WidthProperty); } + set { this.SetValue(WidthProperty, value); } + } + + public double Height + { + get { return this.GetValue(HeightProperty); } + set { this.SetValue(HeightProperty, value); } + } + + public double MinWidth + { + get { return this.GetValue(MinWidthProperty); } + set { this.SetValue(MinWidthProperty, value); } + } + + public double MaxWidth + { + get { return this.GetValue(MaxWidthProperty); } + set { this.SetValue(MaxWidthProperty, value); } + } + + public double MinHeight + { + get { return this.GetValue(MinHeightProperty); } + set { this.SetValue(MinHeightProperty, value); } + } + + public double MaxHeight + { + get { return this.GetValue(MaxHeightProperty); } + set { this.SetValue(MaxHeightProperty, value); } + } + + public Thickness Margin + { + get { return this.GetValue(MarginProperty); } + set { this.SetValue(MarginProperty, value); } + } + + public HorizontalAlignment HorizontalAlignment + { + get { return this.GetValue(HorizontalAlignmentProperty); } + set { this.SetValue(HorizontalAlignmentProperty, value); } + } + + public VerticalAlignment VerticalAlignment + { + get { return this.GetValue(VerticalAlignmentProperty); } + set { this.SetValue(VerticalAlignmentProperty, value); } + } + + public Size ActualSize + { + get { return ((IVisual)this).Bounds.Size; } + } + + public Size? DesiredSize + { + get; + set; + } + + public void Measure(Size availableSize) + { + availableSize = availableSize.Deflate(this.Margin); + this.DesiredSize = this.MeasureCore(availableSize).Constrain(availableSize); + } + + public void Arrange(Rect rect) + { + if (this.DesiredSize.HasValue) + { + this.ArrangeCore(rect); + } + } + + public void InvalidateMeasure() + { + ILayoutRoot root = this.GetLayoutRoot(); + + if (root != null && root.LayoutManager != null) + { + root.LayoutManager.InvalidateMeasure(this); + } + } + + public void InvalidateArrange() + { + ILayoutRoot root = this.GetLayoutRoot(); + + if (root != null) + { + root.LayoutManager.InvalidateArrange(this); + } + } + + protected static void AffectsArrange(PerspexProperty property) + { + property.Changed.Subscribe(AffectsArrangeInvalidate); + } + + protected static void AffectsMeasure(PerspexProperty property) + { + property.Changed.Subscribe(AffectsMeasureInvalidate); + } + + protected virtual void ArrangeCore(Rect finalRect) + { + double originX = finalRect.X + this.Margin.Left; + double originY = finalRect.Y + this.Margin.Top; + double sizeX = Math.Max(0, finalRect.Width - this.Margin.Left - this.Margin.Right); + double sizeY = Math.Max(0, finalRect.Height - this.Margin.Top - this.Margin.Bottom); + + if (this.HorizontalAlignment != HorizontalAlignment.Stretch) + { + sizeX = Math.Min(sizeX, this.DesiredSize.Value.Width); + } + + if (this.VerticalAlignment != VerticalAlignment.Stretch) + { + sizeY = Math.Min(sizeY, this.DesiredSize.Value.Height); + } + + Size taken = this.ArrangeOverride(new Size(sizeX, sizeY)); + + sizeX = Math.Min(taken.Width, sizeX); + sizeY = Math.Min(taken.Height, sizeY); + + switch (this.HorizontalAlignment) + { + case HorizontalAlignment.Center: + originX += (finalRect.Width - sizeX) / 2; + break; + case HorizontalAlignment.Right: + originX += finalRect.Width - sizeX; + break; + } + + switch (this.VerticalAlignment) + { + case VerticalAlignment.Center: + originY += (finalRect.Height - sizeY) / 2; + break; + case VerticalAlignment.Bottom: + originY += finalRect.Height - sizeY; + break; + } + + this.SetVisualBounds(new Rect(originX, originY, sizeX, sizeY)); + } + + protected virtual Size ArrangeOverride(Size finalSize) + { + foreach (ILayoutable child in this.GetVisualChildren().OfType()) + { + child.Arrange(new Rect(finalSize)); + } + + return finalSize; + } + + protected virtual Size MeasureCore(Size availableSize) + { + if (this.IsVisible) + { + availableSize = LayoutHelper.ApplyLayoutConstraints(this, availableSize) + .Deflate(this.Margin); + var measured = this.MeasureOverride(availableSize); + + return measured.Inflate(this.Margin); + } + else + { + return new Size(); + } + } + + protected virtual Size MeasureOverride(Size availableSize) + { + double width = 0; + double height = 0; + + foreach (ILayoutable child in this.GetVisualChildren().OfType()) + { + child.Measure(availableSize); + width = Math.Max(width, child.DesiredSize.Value.Width); + height= Math.Max(height, child.DesiredSize.Value.Height); + } + + return new Size(width, height); + } + + private static void AffectsArrangeInvalidate(PerspexPropertyChangedEventArgs e) + { + ILayoutable control = e.Sender as ILayoutable; + + if (control != null) + { + control.InvalidateArrange(); + } + } + + private static void AffectsMeasureInvalidate(PerspexPropertyChangedEventArgs e) + { + ILayoutable control = e.Sender as ILayoutable; + + if (control != null) + { + control.InvalidateMeasure(); + } + } + + private ILayoutRoot GetLayoutRoot() + { + return this.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + } + } +} diff --git a/Perspex.Layout/Perspex.Layout.csproj b/Perspex.Layout/Perspex.Layout.csproj new file mode 100644 index 0000000000..275921bea5 --- /dev/null +++ b/Perspex.Layout/Perspex.Layout.csproj @@ -0,0 +1,88 @@ + + + + + 11.0 + Debug + AnyCPU + {42472427-4774-4C81-8AFF-9F27B8E31721} + Library + Properties + Perspex + Perspex.Layout + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + + + + + + + + + + + + ..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll + + + ..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll + + + ..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll + + + ..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll + + + + + + + + + + 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}. + + + + + \ No newline at end of file diff --git a/Perspex.Layout/Properties/AssemblyInfo.cs b/Perspex.Layout/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c157837301 --- /dev/null +++ b/Perspex.Layout/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Layout")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Layout")] +[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")] diff --git a/Perspex.Layout/packages.config b/Perspex.Layout/packages.config new file mode 100644 index 0000000000..af8b5868bf --- /dev/null +++ b/Perspex.Layout/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Perspex.SceneGraph/IVisual.cs b/Perspex.SceneGraph/IVisual.cs new file mode 100644 index 0000000000..0b256d5fac --- /dev/null +++ b/Perspex.SceneGraph/IVisual.cs @@ -0,0 +1,67 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System.Collections.Generic; + using Perspex.Media; + + /// + /// Represents a node in the visual scene graph. + /// + /// + /// The interface defines the interface required for a renderer to + /// render a scene graph. You should not usually need to reference this interface unless + /// you are writing a renderer; instead use the extension methods defined in + /// to traverse the scene graph. This interface is + /// implemented by . It should not be necessary to implement it + /// anywhere else. + /// + public interface IVisual + { + /// + /// Gets the bounds of the scene graph node. + /// + /// + Rect Bounds { get; } + + /// + /// Gets a value indicating whether this scene graph node is visible. + /// + bool IsVisible { get; } + + /// + /// Gets the opacity of the scene graph node. + /// + double Opacity { get; } + + /// + /// Gets the render transform of the scene graph node. + /// + ITransform RenderTransform { get; } + + /// + /// Gets the transform origin of the scene graph node. + /// + Origin TransformOrigin { get; } + + /// + /// Gets the scene graph node's child nodes. + /// + IEnumerable VisualChildren { get; } + + /// + /// Gets the scene graph node's parent node. + /// + IVisual VisualParent { get; } + + /// + /// Renders the scene graph node to a . + /// + /// The context. + void Render(IDrawingContext context); + } +} diff --git a/Perspex.SceneGraph/Matrix.cs b/Perspex.SceneGraph/Matrix.cs new file mode 100644 index 0000000000..a262081e02 --- /dev/null +++ b/Perspex.SceneGraph/Matrix.cs @@ -0,0 +1,182 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + + public struct Matrix + { + private double m11; + private double m12; + private double m21; + private double m22; + private double offsetX; + private double offsetY; + + public Matrix( + double m11, + double m12, + double m21, + double m22, + double offsetX, + double offsetY) + { + this.m11 = m11; + this.m12 = m12; + this.m21 = m21; + this.m22 = m22; + this.offsetX = offsetX; + this.offsetY = offsetY; + } + + public static Matrix Identity + { + get { return new Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); } + } + + public double Determinant + { + get { return (this.m11 * this.m22) - (this.m12 * this.m21); } + } + + public bool HasInverse + { + get { return this.Determinant != 0; } + } + + public bool IsIdentity + { + get { return this.Equals(Matrix.Identity); } + } + + public double M11 + { + get { return this.m11; } + } + + public double M12 + { + get { return this.m12; } + } + + public double M21 + { + get { return this.m21; } + } + + public double M22 + { + get { return this.m22; } + } + + public double OffsetX + { + get { return this.offsetX; } + } + + public double OffsetY + { + get { return this.offsetY; } + } + + public static Matrix operator *(Matrix left, Matrix right) + { + return new Matrix( + (left.M11 * right.M11) + (left.M12 * right.M21), + (left.M11 * right.M12) + (left.M12 * right.M22), + (left.M21 * right.M11) + (left.M22 * right.M21), + (left.M21 * right.M12) + (left.M22 * right.M22), + (left.offsetX * right.M11) + (left.offsetY * right.M21) + right.offsetX, + (left.offsetX * right.M12) + (left.offsetY * right.M22) + right.offsetY); + } + + public static bool Equals(Matrix matrix1, Matrix matrix2) + { + return matrix1.Equals(matrix2); + } + + public static Matrix Rotation(double angle) + { + double cos = Math.Cos(angle); + double sin = Math.Sin(angle); + return new Matrix(cos, sin, -sin, cos, 0, 0); + } + + public static Matrix Translation(Vector v) + { + return Translation(v.X, v.Y); + } + + public static Matrix Translation(double x, double y) + { + return new Matrix(1.0, 0.0, 0.0, 1.0, x, y); + } + + public static double ToRadians(double angle) + { + return angle * 0.0174532925; + } + + public static Matrix operator -(Matrix matrix) + { + return matrix.Invert(); + } + + public static bool operator ==(Matrix matrix1, Matrix matrix2) + { + return matrix1.Equals(matrix2); + } + + public static bool operator !=(Matrix matrix1, Matrix matrix2) + { + return !matrix1.Equals(matrix2); + } + + public bool Equals(Matrix value) + { + return this.m11 == value.M11 && + this.m12 == value.M12 && + this.m21 == value.M21 && + this.m22 == value.M22 && + this.offsetX == value.OffsetX && + this.offsetY == value.OffsetY; + } + + public override bool Equals(object o) + { + if (!(o is Matrix)) + { + return false; + } + + return this.Equals((Matrix)o); + } + + public override int GetHashCode() + { + throw new NotImplementedException(); + } + + public Matrix Invert() + { + if (!this.HasInverse) + { + throw new InvalidOperationException("Transform is not invertible."); + } + + double d = this.Determinant; + + return new Matrix( + this.m22 / d, + -this.m12 / d, + -this.m21 / d, + this.m11 / d, + ((this.m21 * this.offsetY) - (this.m22 * this.offsetX)) / d, + ((this.m12 * this.offsetX) - (this.m11 * this.offsetY)) / d); + } + } +} \ No newline at end of file diff --git a/Perspex.SceneGraph/Media/Brush.cs b/Perspex.SceneGraph/Media/Brush.cs new file mode 100644 index 0000000000..f86a7d2560 --- /dev/null +++ b/Perspex.SceneGraph/Media/Brush.cs @@ -0,0 +1,15 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + /// + /// Describes how an area is painted. + /// + public abstract class Brush + { + } +} diff --git a/Perspex.SceneGraph/Media/Brushes.cs b/Perspex.SceneGraph/Media/Brushes.cs new file mode 100644 index 0000000000..998a5f97d5 --- /dev/null +++ b/Perspex.SceneGraph/Media/Brushes.cs @@ -0,0 +1,301 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + /// + /// Predefined brushes. + /// + public static class Brushes + { + static Brushes() + { + AliceBlue = new SolidColorBrush(Colors.AliceBlue); + AntiqueWhite = new SolidColorBrush(Colors.AntiqueWhite); + Aqua = new SolidColorBrush(Colors.Aqua); + Aquamarine = new SolidColorBrush(Colors.Aquamarine); + Azure = new SolidColorBrush(Colors.Azure); + Beige = new SolidColorBrush(Colors.Beige); + Bisque = new SolidColorBrush(Colors.Bisque); + Black = new SolidColorBrush(Colors.Black); + BlanchedAlmond = new SolidColorBrush(Colors.BlanchedAlmond); + Blue = new SolidColorBrush(Colors.Blue); + BlueViolet = new SolidColorBrush(Colors.BlueViolet); + Brown = new SolidColorBrush(Colors.Brown); + BurlyWood = new SolidColorBrush(Colors.BurlyWood); + CadetBlue = new SolidColorBrush(Colors.CadetBlue); + Chartreuse = new SolidColorBrush(Colors.Chartreuse); + Chocolate = new SolidColorBrush(Colors.Chocolate); + Coral = new SolidColorBrush(Colors.Coral); + CornflowerBlue = new SolidColorBrush(Colors.CornflowerBlue); + Cornsilk = new SolidColorBrush(Colors.Cornsilk); + Crimson = new SolidColorBrush(Colors.Crimson); + Cyan = new SolidColorBrush(Colors.Cyan); + DarkBlue = new SolidColorBrush(Colors.DarkBlue); + DarkCyan = new SolidColorBrush(Colors.DarkCyan); + DarkGoldenrod = new SolidColorBrush(Colors.DarkGoldenrod); + DarkGray = new SolidColorBrush(Colors.DarkGray); + DarkGreen = new SolidColorBrush(Colors.DarkGreen); + DarkKhaki = new SolidColorBrush(Colors.DarkKhaki); + DarkMagenta = new SolidColorBrush(Colors.DarkMagenta); + DarkOliveGreen = new SolidColorBrush(Colors.DarkOliveGreen); + DarkOrange = new SolidColorBrush(Colors.DarkOrange); + DarkOrchid = new SolidColorBrush(Colors.DarkOrchid); + DarkRed = new SolidColorBrush(Colors.DarkRed); + DarkSalmon = new SolidColorBrush(Colors.DarkSalmon); + DarkSeaGreen = new SolidColorBrush(Colors.DarkSeaGreen); + DarkSlateBlue = new SolidColorBrush(Colors.DarkSlateBlue); + DarkSlateGray = new SolidColorBrush(Colors.DarkSlateGray); + DarkTurquoise = new SolidColorBrush(Colors.DarkTurquoise); + DarkViolet = new SolidColorBrush(Colors.DarkViolet); + DeepPink = new SolidColorBrush(Colors.DeepPink); + DeepSkyBlue = new SolidColorBrush(Colors.DeepSkyBlue); + DimGray = new SolidColorBrush(Colors.DimGray); + DodgerBlue = new SolidColorBrush(Colors.DodgerBlue); + Firebrick = new SolidColorBrush(Colors.Firebrick); + FloralWhite = new SolidColorBrush(Colors.FloralWhite); + ForestGreen = new SolidColorBrush(Colors.ForestGreen); + Fuchsia = new SolidColorBrush(Colors.Fuchsia); + Gainsboro = new SolidColorBrush(Colors.Gainsboro); + GhostWhite = new SolidColorBrush(Colors.GhostWhite); + Gold = new SolidColorBrush(Colors.Gold); + Goldenrod = new SolidColorBrush(Colors.Goldenrod); + Gray = new SolidColorBrush(Colors.Gray); + Green = new SolidColorBrush(Colors.Green); + GreenYellow = new SolidColorBrush(Colors.GreenYellow); + Honeydew = new SolidColorBrush(Colors.Honeydew); + HotPink = new SolidColorBrush(Colors.HotPink); + IndianRed = new SolidColorBrush(Colors.IndianRed); + Indigo = new SolidColorBrush(Colors.Indigo); + Ivory = new SolidColorBrush(Colors.Ivory); + Khaki = new SolidColorBrush(Colors.Khaki); + Lavender = new SolidColorBrush(Colors.Lavender); + LavenderBlush = new SolidColorBrush(Colors.LavenderBlush); + LawnGreen = new SolidColorBrush(Colors.LawnGreen); + LemonChiffon = new SolidColorBrush(Colors.LemonChiffon); + LightBlue = new SolidColorBrush(Colors.LightBlue); + LightCoral = new SolidColorBrush(Colors.LightCoral); + LightCyan = new SolidColorBrush(Colors.LightCyan); + LightGoldenrodYellow = new SolidColorBrush(Colors.LightGoldenrodYellow); + LightGray = new SolidColorBrush(Colors.LightGray); + LightGreen = new SolidColorBrush(Colors.LightGreen); + LightPink = new SolidColorBrush(Colors.LightPink); + LightSalmon = new SolidColorBrush(Colors.LightSalmon); + LightSeaGreen = new SolidColorBrush(Colors.LightSeaGreen); + LightSkyBlue = new SolidColorBrush(Colors.LightSkyBlue); + LightSlateGray = new SolidColorBrush(Colors.LightSlateGray); + LightSteelBlue = new SolidColorBrush(Colors.LightSteelBlue); + LightYellow = new SolidColorBrush(Colors.LightYellow); + Lime = new SolidColorBrush(Colors.Lime); + LimeGreen = new SolidColorBrush(Colors.LimeGreen); + Linen = new SolidColorBrush(Colors.Linen); + Magenta = new SolidColorBrush(Colors.Magenta); + Maroon = new SolidColorBrush(Colors.Maroon); + MediumAquamarine = new SolidColorBrush(Colors.MediumAquamarine); + MediumBlue = new SolidColorBrush(Colors.MediumBlue); + MediumOrchid = new SolidColorBrush(Colors.MediumOrchid); + MediumPurple = new SolidColorBrush(Colors.MediumPurple); + MediumSeaGreen = new SolidColorBrush(Colors.MediumSeaGreen); + MediumSlateBlue = new SolidColorBrush(Colors.MediumSlateBlue); + MediumSpringGreen = new SolidColorBrush(Colors.MediumSpringGreen); + MediumTurquoise = new SolidColorBrush(Colors.MediumTurquoise); + MediumVioletRed = new SolidColorBrush(Colors.MediumVioletRed); + MidnightBlue = new SolidColorBrush(Colors.MidnightBlue); + MintCream = new SolidColorBrush(Colors.MintCream); + MistyRose = new SolidColorBrush(Colors.MistyRose); + Moccasin = new SolidColorBrush(Colors.Moccasin); + NavajoWhite = new SolidColorBrush(Colors.NavajoWhite); + Navy = new SolidColorBrush(Colors.Navy); + OldLace = new SolidColorBrush(Colors.OldLace); + Olive = new SolidColorBrush(Colors.Olive); + OliveDrab = new SolidColorBrush(Colors.OliveDrab); + Orange = new SolidColorBrush(Colors.Orange); + OrangeRed = new SolidColorBrush(Colors.OrangeRed); + Orchid = new SolidColorBrush(Colors.Orchid); + PaleGoldenrod = new SolidColorBrush(Colors.PaleGoldenrod); + PaleGreen = new SolidColorBrush(Colors.PaleGreen); + PaleTurquoise = new SolidColorBrush(Colors.PaleTurquoise); + PaleVioletRed = new SolidColorBrush(Colors.PaleVioletRed); + PapayaWhip = new SolidColorBrush(Colors.PapayaWhip); + PeachPuff = new SolidColorBrush(Colors.PeachPuff); + Peru = new SolidColorBrush(Colors.Peru); + Pink = new SolidColorBrush(Colors.Pink); + Plum = new SolidColorBrush(Colors.Plum); + PowderBlue = new SolidColorBrush(Colors.PowderBlue); + Purple = new SolidColorBrush(Colors.Purple); + Red = new SolidColorBrush(Colors.Red); + RosyBrown = new SolidColorBrush(Colors.RosyBrown); + RoyalBlue = new SolidColorBrush(Colors.RoyalBlue); + SaddleBrown = new SolidColorBrush(Colors.SaddleBrown); + Salmon = new SolidColorBrush(Colors.Salmon); + SandyBrown = new SolidColorBrush(Colors.SandyBrown); + SeaGreen = new SolidColorBrush(Colors.SeaGreen); + SeaShell = new SolidColorBrush(Colors.SeaShell); + Sienna = new SolidColorBrush(Colors.Sienna); + Silver = new SolidColorBrush(Colors.Silver); + SkyBlue = new SolidColorBrush(Colors.SkyBlue); + SlateBlue = new SolidColorBrush(Colors.SlateBlue); + SlateGray = new SolidColorBrush(Colors.SlateGray); + Snow = new SolidColorBrush(Colors.Snow); + SpringGreen = new SolidColorBrush(Colors.SpringGreen); + SteelBlue = new SolidColorBrush(Colors.SteelBlue); + Tan = new SolidColorBrush(Colors.Tan); + Teal = new SolidColorBrush(Colors.Teal); + Thistle = new SolidColorBrush(Colors.Thistle); + Tomato = new SolidColorBrush(Colors.Tomato); + Transparent = new SolidColorBrush(Colors.Transparent); + Turquoise = new SolidColorBrush(Colors.Turquoise); + Violet = new SolidColorBrush(Colors.Violet); + Wheat = new SolidColorBrush(Colors.Wheat); + White = new SolidColorBrush(Colors.White); + WhiteSmoke = new SolidColorBrush(Colors.WhiteSmoke); + Yellow = new SolidColorBrush(Colors.Yellow); + YellowGreen = new SolidColorBrush(Colors.YellowGreen); + } + + public static SolidColorBrush AliceBlue { get; private set; } + public static SolidColorBrush AntiqueWhite { get; private set; } + public static SolidColorBrush Aqua { get; private set; } + public static SolidColorBrush Aquamarine { get; private set; } + public static SolidColorBrush Azure { get; private set; } + public static SolidColorBrush Beige { get; private set; } + public static SolidColorBrush Bisque { get; private set; } + public static SolidColorBrush Black { get; private set; } + public static SolidColorBrush BlanchedAlmond { get; private set; } + public static SolidColorBrush Blue { get; private set; } + public static SolidColorBrush BlueViolet { get; private set; } + public static SolidColorBrush Brown { get; private set; } + public static SolidColorBrush BurlyWood { get; private set; } + public static SolidColorBrush CadetBlue { get; private set; } + public static SolidColorBrush Chartreuse { get; private set; } + public static SolidColorBrush Chocolate { get; private set; } + public static SolidColorBrush Coral { get; private set; } + public static SolidColorBrush CornflowerBlue { get; private set; } + public static SolidColorBrush Cornsilk { get; private set; } + public static SolidColorBrush Crimson { get; private set; } + public static SolidColorBrush Cyan { get; private set; } + public static SolidColorBrush DarkBlue { get; private set; } + public static SolidColorBrush DarkCyan { get; private set; } + public static SolidColorBrush DarkGoldenrod { get; private set; } + public static SolidColorBrush DarkGray { get; private set; } + public static SolidColorBrush DarkGreen { get; private set; } + public static SolidColorBrush DarkKhaki { get; private set; } + public static SolidColorBrush DarkMagenta { get; private set; } + public static SolidColorBrush DarkOliveGreen { get; private set; } + public static SolidColorBrush DarkOrange { get; private set; } + public static SolidColorBrush DarkOrchid { get; private set; } + public static SolidColorBrush DarkRed { get; private set; } + public static SolidColorBrush DarkSalmon { get; private set; } + public static SolidColorBrush DarkSeaGreen { get; private set; } + public static SolidColorBrush DarkSlateBlue { get; private set; } + public static SolidColorBrush DarkSlateGray { get; private set; } + public static SolidColorBrush DarkTurquoise { get; private set; } + public static SolidColorBrush DarkViolet { get; private set; } + public static SolidColorBrush DeepPink { get; private set; } + public static SolidColorBrush DeepSkyBlue { get; private set; } + public static SolidColorBrush DimGray { get; private set; } + public static SolidColorBrush DodgerBlue { get; private set; } + public static SolidColorBrush Firebrick { get; private set; } + public static SolidColorBrush FloralWhite { get; private set; } + public static SolidColorBrush ForestGreen { get; private set; } + public static SolidColorBrush Fuchsia { get; private set; } + public static SolidColorBrush Gainsboro { get; private set; } + public static SolidColorBrush GhostWhite { get; private set; } + public static SolidColorBrush Gold { get; private set; } + public static SolidColorBrush Goldenrod { get; private set; } + public static SolidColorBrush Gray { get; private set; } + public static SolidColorBrush Green { get; private set; } + public static SolidColorBrush GreenYellow { get; private set; } + public static SolidColorBrush Honeydew { get; private set; } + public static SolidColorBrush HotPink { get; private set; } + public static SolidColorBrush IndianRed { get; private set; } + public static SolidColorBrush Indigo { get; private set; } + public static SolidColorBrush Ivory { get; private set; } + public static SolidColorBrush Khaki { get; private set; } + public static SolidColorBrush Lavender { get; private set; } + public static SolidColorBrush LavenderBlush { get; private set; } + public static SolidColorBrush LawnGreen { get; private set; } + public static SolidColorBrush LemonChiffon { get; private set; } + public static SolidColorBrush LightBlue { get; private set; } + public static SolidColorBrush LightCoral { get; private set; } + public static SolidColorBrush LightCyan { get; private set; } + public static SolidColorBrush LightGoldenrodYellow { get; private set; } + public static SolidColorBrush LightGray { get; private set; } + public static SolidColorBrush LightGreen { get; private set; } + public static SolidColorBrush LightPink { get; private set; } + public static SolidColorBrush LightSalmon { get; private set; } + public static SolidColorBrush LightSeaGreen { get; private set; } + public static SolidColorBrush LightSkyBlue { get; private set; } + public static SolidColorBrush LightSlateGray { get; private set; } + public static SolidColorBrush LightSteelBlue { get; private set; } + public static SolidColorBrush LightYellow { get; private set; } + public static SolidColorBrush Lime { get; private set; } + public static SolidColorBrush LimeGreen { get; private set; } + public static SolidColorBrush Linen { get; private set; } + public static SolidColorBrush Magenta { get; private set; } + public static SolidColorBrush Maroon { get; private set; } + public static SolidColorBrush MediumAquamarine { get; private set; } + public static SolidColorBrush MediumBlue { get; private set; } + public static SolidColorBrush MediumOrchid { get; private set; } + public static SolidColorBrush MediumPurple { get; private set; } + public static SolidColorBrush MediumSeaGreen { get; private set; } + public static SolidColorBrush MediumSlateBlue { get; private set; } + public static SolidColorBrush MediumSpringGreen { get; private set; } + public static SolidColorBrush MediumTurquoise { get; private set; } + public static SolidColorBrush MediumVioletRed { get; private set; } + public static SolidColorBrush MidnightBlue { get; private set; } + public static SolidColorBrush MintCream { get; private set; } + public static SolidColorBrush MistyRose { get; private set; } + public static SolidColorBrush Moccasin { get; private set; } + public static SolidColorBrush NavajoWhite { get; private set; } + public static SolidColorBrush Navy { get; private set; } + public static SolidColorBrush OldLace { get; private set; } + public static SolidColorBrush Olive { get; private set; } + public static SolidColorBrush OliveDrab { get; private set; } + public static SolidColorBrush Orange { get; private set; } + public static SolidColorBrush OrangeRed { get; private set; } + public static SolidColorBrush Orchid { get; private set; } + public static SolidColorBrush PaleGoldenrod { get; private set; } + public static SolidColorBrush PaleGreen { get; private set; } + public static SolidColorBrush PaleTurquoise { get; private set; } + public static SolidColorBrush PaleVioletRed { get; private set; } + public static SolidColorBrush PapayaWhip { get; private set; } + public static SolidColorBrush PeachPuff { get; private set; } + public static SolidColorBrush Peru { get; private set; } + public static SolidColorBrush Pink { get; private set; } + public static SolidColorBrush Plum { get; private set; } + public static SolidColorBrush PowderBlue { get; private set; } + public static SolidColorBrush Purple { get; private set; } + public static SolidColorBrush Red { get; private set; } + public static SolidColorBrush RosyBrown { get; private set; } + public static SolidColorBrush RoyalBlue { get; private set; } + public static SolidColorBrush SaddleBrown { get; private set; } + public static SolidColorBrush Salmon { get; private set; } + public static SolidColorBrush SandyBrown { get; private set; } + public static SolidColorBrush SeaGreen { get; private set; } + public static SolidColorBrush SeaShell { get; private set; } + public static SolidColorBrush Sienna { get; private set; } + public static SolidColorBrush Silver { get; private set; } + public static SolidColorBrush SkyBlue { get; private set; } + public static SolidColorBrush SlateBlue { get; private set; } + public static SolidColorBrush SlateGray { get; private set; } + public static SolidColorBrush Snow { get; private set; } + public static SolidColorBrush SpringGreen { get; private set; } + public static SolidColorBrush SteelBlue { get; private set; } + public static SolidColorBrush Tan { get; private set; } + public static SolidColorBrush Teal { get; private set; } + public static SolidColorBrush Thistle { get; private set; } + public static SolidColorBrush Tomato { get; private set; } + public static SolidColorBrush Transparent { get; private set; } + public static SolidColorBrush Turquoise { get; private set; } + public static SolidColorBrush Violet { get; private set; } + public static SolidColorBrush Wheat { get; private set; } + public static SolidColorBrush White { get; private set; } + public static SolidColorBrush WhiteSmoke { get; private set; } + public static SolidColorBrush Yellow { get; private set; } + public static SolidColorBrush YellowGreen { get; private set; } + } +} diff --git a/Perspex.SceneGraph/Media/Color.cs b/Perspex.SceneGraph/Media/Color.cs new file mode 100644 index 0000000000..c070d3d9a3 --- /dev/null +++ b/Perspex.SceneGraph/Media/Color.cs @@ -0,0 +1,110 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + /// + /// An ARGB color. + /// + public struct Color + { + /// + /// Gets or sets the Alpha component of the color. + /// + public byte A { get; set; } + + /// + /// Gets or sets the Red component of the color. + /// + public byte R { get; set; } + + /// + /// Gets or sets the Green component of the color. + /// + public byte G { get; set; } + + /// + /// Gets or sets the Blue component of the color. + /// + public byte B { get; set; } + + /// + /// Creates a from alpha, red, green and blue components. + /// + /// The alpha component. + /// The red component. + /// The green component. + /// The blue component. + /// The color. + public static Color FromArgb(byte a, byte r, byte g, byte b) + { + return new Color + { + A = a, + R = r, + G = g, + B = b, + }; + } + + /// + /// Creates a from red, green and blue components. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The color. + public static Color FromRgb(byte r, byte g, byte b) + { + return new Color + { + A = 0xff, + R = r, + G = g, + B = b, + }; + } + + /// + /// Creates a from an integer. + /// + /// The integer value. + /// The color. + public static Color FromUInt32(uint value) + { + return new Color + { + A = (byte)((value >> 24) & 0xff), + R = (byte)((value >> 16) & 0xff), + G = (byte)((value >> 8) & 0xff), + B = (byte)(value & 0xff), + }; + } + + /// + /// Returns the string representation of the color. + /// + /// + /// The string representation of the color. + /// + public override string ToString() + { + uint rgb = ((uint)this.A << 24) | ((uint)this.R << 16) | ((uint)this.G << 8) | (uint)this.B; + return string.Format("#{0:x8}", rgb); + } + + /// + /// Returns the integer representation of the color. + /// + /// + /// The integer representation of the color. + /// + public uint ToUint32() + { + return ((uint)this.A << 24) | ((uint)this.R << 16) | ((uint)this.G << 8) | (uint)this.B; + } + } +} diff --git a/Perspex.SceneGraph/Media/Colors.cs b/Perspex.SceneGraph/Media/Colors.cs new file mode 100644 index 0000000000..6a48e4226c --- /dev/null +++ b/Perspex.SceneGraph/Media/Colors.cs @@ -0,0 +1,716 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + public sealed class Colors + { + public static Color AliceBlue + { + get { return Color.FromUInt32(0xfff0f8ff); } + } + + public static Color AntiqueWhite + { + get { return Color.FromUInt32(0xfffaebd7); } + } + + public static Color Aqua + { + get { return Color.FromUInt32(0xff00ffff); } + } + + public static Color Aquamarine + { + get { return Color.FromUInt32(0xff7fffd4); } + } + + public static Color Azure + { + get { return Color.FromUInt32(0xfff0ffff); } + } + + public static Color Beige + { + get { return Color.FromUInt32(0xfff5f5dc); } + } + + public static Color Bisque + { + get { return Color.FromUInt32(0xffffe4c4); } + } + + public static Color Black + { + get { return Color.FromUInt32(0xff000000); } + } + + public static Color BlanchedAlmond + { + get { return Color.FromUInt32(0xffffebcd); } + } + + public static Color Blue + { + get { return Color.FromUInt32(0xff0000ff); } + } + + public static Color BlueViolet + { + get { return Color.FromUInt32(0xff8a2be2); } + } + + public static Color Brown + { + get { return Color.FromUInt32(0xffa52a2a); } + } + + public static Color BurlyWood + { + get { return Color.FromUInt32(0xffdeb887); } + } + + public static Color CadetBlue + { + get { return Color.FromUInt32(0xff5f9ea0); } + } + + public static Color Chartreuse + { + get { return Color.FromUInt32(0xff7fff00); } + } + + public static Color Chocolate + { + get { return Color.FromUInt32(0xffd2691e); } + } + + public static Color Coral + { + get { return Color.FromUInt32(0xffff7f50); } + } + + public static Color CornflowerBlue + { + get { return Color.FromUInt32(0xff6495ed); } + } + + public static Color Cornsilk + { + get { return Color.FromUInt32(0xfffff8dc); } + } + + public static Color Crimson + { + get { return Color.FromUInt32(0xffdc143c); } + } + + public static Color Cyan + { + get { return Color.FromUInt32(0xff00ffff); } + } + + public static Color DarkBlue + { + get { return Color.FromUInt32(0xff00008b); } + } + + public static Color DarkCyan + { + get { return Color.FromUInt32(0xff008b8b); } + } + + public static Color DarkGoldenrod + { + get { return Color.FromUInt32(0xffb8860b); } + } + + public static Color DarkGray + { + get { return Color.FromUInt32(0xffa9a9a9); } + } + + public static Color DarkGreen + { + get { return Color.FromUInt32(0xff006400); } + } + + public static Color DarkKhaki + { + get { return Color.FromUInt32(0xffbdb76b); } + } + + public static Color DarkMagenta + { + get { return Color.FromUInt32(0xff8b008b); } + } + + public static Color DarkOliveGreen + { + get { return Color.FromUInt32(0xff556b2f); } + } + + public static Color DarkOrange + { + get { return Color.FromUInt32(0xffff8c00); } + } + + public static Color DarkOrchid + { + get { return Color.FromUInt32(0xff9932cc); } + } + + public static Color DarkRed + { + get { return Color.FromUInt32(0xff8b0000); } + } + + public static Color DarkSalmon + { + get { return Color.FromUInt32(0xffe9967a); } + } + + public static Color DarkSeaGreen + { + get { return Color.FromUInt32(0xff8fbc8f); } + } + + public static Color DarkSlateBlue + { + get { return Color.FromUInt32(0xff483d8b); } + } + + public static Color DarkSlateGray + { + get { return Color.FromUInt32(0xff2f4f4f); } + } + + public static Color DarkTurquoise + { + get { return Color.FromUInt32(0xff00ced1); } + } + + public static Color DarkViolet + { + get { return Color.FromUInt32(0xff9400d3); } + } + + public static Color DeepPink + { + get { return Color.FromUInt32(0xffff1493); } + } + + public static Color DeepSkyBlue + { + get { return Color.FromUInt32(0xff00bfff); } + } + + public static Color DimGray + { + get { return Color.FromUInt32(0xff696969); } + } + + public static Color DodgerBlue + { + get { return Color.FromUInt32(0xff1e90ff); } + } + + public static Color Firebrick + { + get { return Color.FromUInt32(0xffb22222); } + } + + public static Color FloralWhite + { + get { return Color.FromUInt32(0xfffffaf0); } + } + + public static Color ForestGreen + { + get { return Color.FromUInt32(0xff228b22); } + } + + public static Color Fuchsia + { + get { return Color.FromUInt32(0xffff00ff); } + } + + public static Color Gainsboro + { + get { return Color.FromUInt32(0xffdcdcdc); } + } + + public static Color GhostWhite + { + get { return Color.FromUInt32(0xfff8f8ff); } + } + + public static Color Gold + { + get { return Color.FromUInt32(0xffffd700); } + } + + public static Color Goldenrod + { + get { return Color.FromUInt32(0xffdaa520); } + } + + public static Color Gray + { + get { return Color.FromUInt32(0xff808080); } + } + + public static Color Green + { + get { return Color.FromUInt32(0xff008000); } + } + + public static Color GreenYellow + { + get { return Color.FromUInt32(0xffadff2f); } + } + + public static Color Honeydew + { + get { return Color.FromUInt32(0xfff0fff0); } + } + + public static Color HotPink + { + get { return Color.FromUInt32(0xffff69b4); } + } + + public static Color IndianRed + { + get { return Color.FromUInt32(0xffcd5c5c); } + } + + public static Color Indigo + { + get { return Color.FromUInt32(0xff4b0082); } + } + + public static Color Ivory + { + get { return Color.FromUInt32(0xfffffff0); } + } + + public static Color Khaki + { + get { return Color.FromUInt32(0xfff0e68c); } + } + + public static Color Lavender + { + get { return Color.FromUInt32(0xffe6e6fa); } + } + + public static Color LavenderBlush + { + get { return Color.FromUInt32(0xfffff0f5); } + } + + public static Color LawnGreen + { + get { return Color.FromUInt32(0xff7cfc00); } + } + + public static Color LemonChiffon + { + get { return Color.FromUInt32(0xfffffacd); } + } + + public static Color LightBlue + { + get { return Color.FromUInt32(0xffadd8e6); } + } + + public static Color LightCoral + { + get { return Color.FromUInt32(0xfff08080); } + } + + public static Color LightCyan + { + get { return Color.FromUInt32(0xffe0ffff); } + } + + public static Color LightGoldenrodYellow + { + get { return Color.FromUInt32(0xfffafad2); } + } + + public static Color LightGray + { + get { return Color.FromUInt32(0xffd3d3d3); } + } + + public static Color LightGreen + { + get { return Color.FromUInt32(0xff90ee90); } + } + + public static Color LightPink + { + get { return Color.FromUInt32(0xffffb6c1); } + } + + public static Color LightSalmon + { + get { return Color.FromUInt32(0xffffa07a); } + } + + public static Color LightSeaGreen + { + get { return Color.FromUInt32(0xff20b2aa); } + } + + public static Color LightSkyBlue + { + get { return Color.FromUInt32(0xff87cefa); } + } + + public static Color LightSlateGray + { + get { return Color.FromUInt32(0xff778899); } + } + + public static Color LightSteelBlue + { + get { return Color.FromUInt32(0xffb0c4de); } + } + + public static Color LightYellow + { + get { return Color.FromUInt32(0xffffffe0); } + } + + public static Color Lime + { + get { return Color.FromUInt32(0xff00ff00); } + } + + public static Color LimeGreen + { + get { return Color.FromUInt32(0xff32cd32); } + } + + public static Color Linen + { + get { return Color.FromUInt32(0xfffaf0e6); } + } + + public static Color Magenta + { + get { return Color.FromUInt32(0xffff00ff); } + } + + public static Color Maroon + { + get { return Color.FromUInt32(0xff800000); } + } + + public static Color MediumAquamarine + { + get { return Color.FromUInt32(0xff66cdaa); } + } + + public static Color MediumBlue + { + get { return Color.FromUInt32(0xff0000cd); } + } + + public static Color MediumOrchid + { + get { return Color.FromUInt32(0xffba55d3); } + } + + public static Color MediumPurple + { + get { return Color.FromUInt32(0xff9370db); } + } + + public static Color MediumSeaGreen + { + get { return Color.FromUInt32(0xff3cb371); } + } + + public static Color MediumSlateBlue + { + get { return Color.FromUInt32(0xff7b68ee); } + } + + public static Color MediumSpringGreen + { + get { return Color.FromUInt32(0xff00fa9a); } + } + + public static Color MediumTurquoise + { + get { return Color.FromUInt32(0xff48d1cc); } + } + + public static Color MediumVioletRed + { + get { return Color.FromUInt32(0xffc71585); } + } + + public static Color MidnightBlue + { + get { return Color.FromUInt32(0xff191970); } + } + + public static Color MintCream + { + get { return Color.FromUInt32(0xfff5fffa); } + } + + public static Color MistyRose + { + get { return Color.FromUInt32(0xffffe4e1); } + } + + public static Color Moccasin + { + get { return Color.FromUInt32(0xffffe4b5); } + } + + public static Color NavajoWhite + { + get { return Color.FromUInt32(0xffffdead); } + } + + public static Color Navy + { + get { return Color.FromUInt32(0xff000080); } + } + + public static Color OldLace + { + get { return Color.FromUInt32(0xfffdf5e6); } + } + + public static Color Olive + { + get { return Color.FromUInt32(0xff808000); } + } + + public static Color OliveDrab + { + get { return Color.FromUInt32(0xff6b8e23); } + } + + public static Color Orange + { + get { return Color.FromUInt32(0xffffa500); } + } + + public static Color OrangeRed + { + get { return Color.FromUInt32(0xffff4500); } + } + + public static Color Orchid + { + get { return Color.FromUInt32(0xffda70d6); } + } + + public static Color PaleGoldenrod + { + get { return Color.FromUInt32(0xffeee8aa); } + } + + public static Color PaleGreen + { + get { return Color.FromUInt32(0xff98fb98); } + } + + public static Color PaleTurquoise + { + get { return Color.FromUInt32(0xffafeeee); } + } + + public static Color PaleVioletRed + { + get { return Color.FromUInt32(0xffdb7093); } + } + + public static Color PapayaWhip + { + get { return Color.FromUInt32(0xffffefd5); } + } + + public static Color PeachPuff + { + get { return Color.FromUInt32(0xffffdab9); } + } + + public static Color Peru + { + get { return Color.FromUInt32(0xffcd853f); } + } + + public static Color Pink + { + get { return Color.FromUInt32(0xffffc0cb); } + } + + public static Color Plum + { + get { return Color.FromUInt32(0xffdda0dd); } + } + + public static Color PowderBlue + { + get { return Color.FromUInt32(0xffb0e0e6); } + } + + public static Color Purple + { + get { return Color.FromUInt32(0xff800080); } + } + + public static Color Red + { + get { return Color.FromUInt32(0xffff0000); } + } + + public static Color RosyBrown + { + get { return Color.FromUInt32(0xffbc8f8f); } + } + + public static Color RoyalBlue + { + get { return Color.FromUInt32(0xff4169e1); } + } + + public static Color SaddleBrown + { + get { return Color.FromUInt32(0xff8b4513); } + } + + public static Color Salmon + { + get { return Color.FromUInt32(0xfffa8072); } + } + + public static Color SandyBrown + { + get { return Color.FromUInt32(0xfff4a460); } + } + + public static Color SeaGreen + { + get { return Color.FromUInt32(0xff2e8b57); } + } + + public static Color SeaShell + { + get { return Color.FromUInt32(0xfffff5ee); } + } + + public static Color Sienna + { + get { return Color.FromUInt32(0xffa0522d); } + } + + public static Color Silver + { + get { return Color.FromUInt32(0xffc0c0c0); } + } + + public static Color SkyBlue + { + get { return Color.FromUInt32(0xff87ceeb); } + } + + public static Color SlateBlue + { + get { return Color.FromUInt32(0xff6a5acd); } + } + + public static Color SlateGray + { + get { return Color.FromUInt32(0xff708090); } + } + + public static Color Snow + { + get { return Color.FromUInt32(0xfffffafa); } + } + + public static Color SpringGreen + { + get { return Color.FromUInt32(0xff00ff7f); } + } + + public static Color SteelBlue + { + get { return Color.FromUInt32(0xff4682b4); } + } + + public static Color Tan + { + get { return Color.FromUInt32(0xffd2b48c); } + } + + public static Color Teal + { + get { return Color.FromUInt32(0xff008080); } + } + + public static Color Thistle + { + get { return Color.FromUInt32(0xffd8bfd8); } + } + + public static Color Tomato + { + get { return Color.FromUInt32(0xffff6347); } + } + + public static Color Transparent + { + get { return Color.FromUInt32(0x00ffffff); } + } + + public static Color Turquoise + { + get { return Color.FromUInt32(0xff40e0d0); } + } + + public static Color Violet + { + get { return Color.FromUInt32(0xffee82ee); } + } + + public static Color Wheat + { + get { return Color.FromUInt32(0xfff5deb3); } + } + + public static Color White + { + get { return Color.FromUInt32(0xffffffff); } + } + + public static Color WhiteSmoke + { + get { return Color.FromUInt32(0xfff5f5f5); } + } + + public static Color Yellow + { + get { return Color.FromUInt32(0xffffff00); } + } + + public static Color YellowGreen + { + get { return Color.FromUInt32(0xff9acd32); } + } + } +} \ No newline at end of file diff --git a/Perspex.SceneGraph/Media/FormattedText.cs b/Perspex.SceneGraph/Media/FormattedText.cs new file mode 100644 index 0000000000..556d2a9f9f --- /dev/null +++ b/Perspex.SceneGraph/Media/FormattedText.cs @@ -0,0 +1,17 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + public class FormattedText + { + public string FontFamilyName { get; set; } + + public double FontSize { get; set; } + + public string Text { get; set; } + } +} diff --git a/Perspex.SceneGraph/Media/Geometry.cs b/Perspex.SceneGraph/Media/Geometry.cs new file mode 100644 index 0000000000..192842aa28 --- /dev/null +++ b/Perspex.SceneGraph/Media/Geometry.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + using Perspex.Platform; + + public abstract class Geometry + { + public abstract Rect Bounds + { + get; + } + + public IGeometryImpl PlatformImpl + { + get; + protected set; + } + + public Rect GetRenderBounds(double strokeThickness) + { + return this.PlatformImpl.GetRenderBounds(strokeThickness); + } + } +} diff --git a/Perspex.SceneGraph/Media/IDrawingContext.cs b/Perspex.SceneGraph/Media/IDrawingContext.cs new file mode 100644 index 0000000000..0336fcbc94 --- /dev/null +++ b/Perspex.SceneGraph/Media/IDrawingContext.cs @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + using System; + using Perspex.Media.Imaging; + + /// + /// Defines the interface through which drawing occurs. + /// + public interface IDrawingContext : IDisposable + { + Matrix CurrentTransform { get; } + + void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect); + + /// + /// Draws a line. + /// + /// The stroke pen. + /// The first point of the line. + /// The second point of the line. + void DrawLine(Pen pen, Point p1, Point p2); + + /// + /// Draws a geometry. + /// + /// The fill brush. + /// The stroke pen. + /// The geometry. + void DrawGeometry(Brush brush, Pen pen, Geometry geometry); + + /// + /// Draws the outline of a rectangle. + /// + /// The pen. + /// The rectangle bounds. + void DrawRectange(Pen pen, Rect rect); + + /// + /// Draws text. + /// + /// The foreground brush. + /// The bounding rectangle. + /// The text. + void DrawText(Brush foreground, Rect rect, FormattedText text); + + /// + /// Draws a filled rectangle. + /// + /// The brush. + /// The rectangle bounds. + void FillRectange(Brush brush, Rect rect); + + /// + /// Pushes a matrix transformation. + /// + /// The matrix + /// A disposable used to undo the transformation. + IDisposable PushTransform(Matrix matrix); + } +} diff --git a/Perspex.SceneGraph/Media/ITransform.cs b/Perspex.SceneGraph/Media/ITransform.cs new file mode 100644 index 0000000000..9d6e89967b --- /dev/null +++ b/Perspex.SceneGraph/Media/ITransform.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + public interface ITransform + { + Matrix Value { get; } + } +} diff --git a/Perspex.SceneGraph/Media/Imaging/Bitmap.cs b/Perspex.SceneGraph/Media/Imaging/Bitmap.cs new file mode 100644 index 0000000000..9e98886f0e --- /dev/null +++ b/Perspex.SceneGraph/Media/Imaging/Bitmap.cs @@ -0,0 +1,52 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media.Imaging +{ + using Perspex.Platform; + using Splat; + + public class Bitmap : IBitmap + { + public Bitmap(string fileName) + { + IPlatformRenderInterface factory = Locator.Current.GetService(); + this.PlatformImpl = factory.LoadBitmap(fileName); + } + + public Bitmap(int width, int height) + { + IPlatformRenderInterface factory = Locator.Current.GetService(); + this.PlatformImpl = factory.CreateBitmap(width, height); + } + + protected Bitmap(IBitmapImpl impl) + { + this.PlatformImpl = impl; + } + + public int PixelWidth + { + get { return this.PlatformImpl.PixelWidth; } + } + + public int PixelHeight + { + get { return this.PlatformImpl.PixelHeight; } + } + + public IBitmapImpl PlatformImpl + { + get; + private set; + } + + public void Save(string fileName) + { + this.PlatformImpl.Save(fileName); + } + } +} diff --git a/Perspex.SceneGraph/Media/Imaging/IBitmap.cs b/Perspex.SceneGraph/Media/Imaging/IBitmap.cs new file mode 100644 index 0000000000..97a924fbfe --- /dev/null +++ b/Perspex.SceneGraph/Media/Imaging/IBitmap.cs @@ -0,0 +1,17 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media.Imaging +{ + public interface IBitmap + { + int PixelWidth { get; } + + int PixelHeight { get; } + + void Save(string fileName); + } +} diff --git a/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs b/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs new file mode 100644 index 0000000000..3f436941da --- /dev/null +++ b/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs @@ -0,0 +1,35 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media.Imaging +{ + using Perspex.Platform; + using Splat; + + public class RenderTargetBitmap : Bitmap + { + public RenderTargetBitmap(int width, int height) + : base(CreateImpl(width, height)) + { + } + + public new IRenderTargetBitmapImpl PlatformImpl + { + get { return (IRenderTargetBitmapImpl)base.PlatformImpl; } + } + + public void Render(IVisual visual) + { + this.PlatformImpl.Render(visual); + } + + private static IBitmapImpl CreateImpl(int width, int height) + { + IPlatformRenderInterface factory = Locator.Current.GetService(); + return factory.CreateRenderTargetBitmap(width, height); + } + } +} diff --git a/Perspex.SceneGraph/Media/Pen.cs b/Perspex.SceneGraph/Media/Pen.cs new file mode 100644 index 0000000000..5e73c40334 --- /dev/null +++ b/Perspex.SceneGraph/Media/Pen.cs @@ -0,0 +1,46 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + /// + /// Describes how a stroke is drawn. + /// + public class Pen + { + /// + /// Initializes a new instance of the class. + /// + /// The brush used to draw. + /// The stroke thickness. + public Pen(Brush brush, double thickness) + { + this.Brush = brush; + this.Thickness = thickness; + } + + /// + /// Initializes a new instance of the class. + /// + /// The stroke color. + /// The stroke thickness. + public Pen(uint color, double thickness) + { + this.Brush = new SolidColorBrush(color); + this.Thickness = thickness; + } + + /// + /// Gets the brush used to draw the stroke. + /// + public Brush Brush { get; private set; } + + /// + /// Gets the stroke thickness. + /// + public double Thickness { get; private set; } + } +} diff --git a/Perspex.SceneGraph/Media/SolidColorBrush.cs b/Perspex.SceneGraph/Media/SolidColorBrush.cs new file mode 100644 index 0000000000..4210cdaa8c --- /dev/null +++ b/Perspex.SceneGraph/Media/SolidColorBrush.cs @@ -0,0 +1,46 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + /// + /// Fills an area with a solid color. + /// + public class SolidColorBrush : Brush + { + /// + /// Initializes a new instance of the class. + /// + /// The color to use. + public SolidColorBrush(Color color) + { + this.Color = color; + } + + /// + /// Initializes a new instance of the class. + /// + /// The color to use. + public SolidColorBrush(uint color) + : this(Color.FromUInt32(color)) + { + } + + /// + /// Gets the color of the brush. + /// + public Color Color + { + get; + private set; + } + + public override string ToString() + { + return this.Color.ToString(); + } + } +} diff --git a/Perspex.SceneGraph/Media/Stretch.cs b/Perspex.SceneGraph/Media/Stretch.cs new file mode 100644 index 0000000000..354e740f66 --- /dev/null +++ b/Perspex.SceneGraph/Media/Stretch.cs @@ -0,0 +1,16 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + public enum Stretch + { + None, + Fill, + Uniform, + UniformToFill, + } +} diff --git a/Perspex.SceneGraph/Origin.cs b/Perspex.SceneGraph/Origin.cs new file mode 100644 index 0000000000..b4de4562fb --- /dev/null +++ b/Perspex.SceneGraph/Origin.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Globalization; + + public enum OriginUnit + { + Percent, + Pixels, + } + + public struct Origin + { + public static readonly Origin Default = new Origin(0.5, 0.5, OriginUnit.Percent); + + private Point point; + + private OriginUnit unit; + + public Origin(double x, double y, OriginUnit unit) + : this(new Point(x, y), unit) + { + } + + public Origin(Point point, OriginUnit unit) + { + this.point = point; + this.unit = unit; + } + + public Point Point + { + get { return this.point; } + } + + public OriginUnit Unit + { + get { return this.unit; } + } + + public Point ToPixels(Size size) + { + return this.unit == OriginUnit.Pixels ? + point : + new Point(point.X * size.Width, point.Y * size.Height); + } + } +} diff --git a/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/Perspex.SceneGraph/Perspex.SceneGraph.csproj new file mode 100644 index 0000000000..f6f3d2396f --- /dev/null +++ b/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -0,0 +1,116 @@ + + + + + 11.0 + Debug + AnyCPU + {EB582467-6ABB-43A1-B052-E981BA910E3A} + Library + Properties + Perspex + Perspex.SceneGraph + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll + + + ..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll + + + ..\packages\Rx-Interfaces.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Interfaces.dll + + + ..\packages\Rx-Linq.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Linq.dll + + + + + + + + + + + + + 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}. + + + + + \ No newline at end of file diff --git a/Perspex.SceneGraph/Platform/IBitmapImpl.cs b/Perspex.SceneGraph/Platform/IBitmapImpl.cs new file mode 100644 index 0000000000..b6ae7a7ff3 --- /dev/null +++ b/Perspex.SceneGraph/Platform/IBitmapImpl.cs @@ -0,0 +1,17 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + public interface IBitmapImpl + { + int PixelWidth { get; } + + int PixelHeight { get; } + + void Save(string fileName); + } +} diff --git a/Perspex.SceneGraph/Platform/IGeometryImpl.cs b/Perspex.SceneGraph/Platform/IGeometryImpl.cs new file mode 100644 index 0000000000..c9aafd92f4 --- /dev/null +++ b/Perspex.SceneGraph/Platform/IGeometryImpl.cs @@ -0,0 +1,15 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + public interface IGeometryImpl + { + Rect Bounds { get; } + + Rect GetRenderBounds(double strokeThickness); + } +} diff --git a/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs b/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs new file mode 100644 index 0000000000..a5bb3be06c --- /dev/null +++ b/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs @@ -0,0 +1,26 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + using System; + using Perspex.Threading; + + public interface IPlatformRenderInterface + { + ITextService TextService { get; } + + IBitmapImpl CreateBitmap(int width, int height); + + IStreamGeometryImpl CreateStreamGeometry(); + + IRenderer CreateRenderer(IntPtr handle, double width, double height); + + IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height); + + IBitmapImpl LoadBitmap(string fileName); + } +} diff --git a/Perspex.SceneGraph/Platform/IRenderTargetBitmapImpl.cs b/Perspex.SceneGraph/Platform/IRenderTargetBitmapImpl.cs new file mode 100644 index 0000000000..dc20a78304 --- /dev/null +++ b/Perspex.SceneGraph/Platform/IRenderTargetBitmapImpl.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + public interface IRenderTargetBitmapImpl : IBitmapImpl + { + void Render(IVisual visual); + } +} diff --git a/Perspex.SceneGraph/Platform/IRenderer.cs b/Perspex.SceneGraph/Platform/IRenderer.cs new file mode 100644 index 0000000000..e251fe0f6e --- /dev/null +++ b/Perspex.SceneGraph/Platform/IRenderer.cs @@ -0,0 +1,26 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + using System; + + public interface IRenderer + { + /// + /// Renders the specified visual. + /// + /// The visual to render. + void Render(IVisual visual); + + /// + /// Resizes the rendered viewport. + /// + /// The new width. + /// The new height. + void Resize(int width, int height); + } +} diff --git a/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs b/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs new file mode 100644 index 0000000000..cb58199f72 --- /dev/null +++ b/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs @@ -0,0 +1,19 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + using System; + + public interface IStreamGeometryContextImpl : IDisposable + { + void BeginFigure(Point startPoint, bool isFilled); + + void LineTo(Point point); + + void EndFigure(bool isClosed); + } +} diff --git a/Perspex.SceneGraph/Platform/IStreamGeometryImpl.cs b/Perspex.SceneGraph/Platform/IStreamGeometryImpl.cs new file mode 100644 index 0000000000..0f7065ff22 --- /dev/null +++ b/Perspex.SceneGraph/Platform/IStreamGeometryImpl.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + public interface IStreamGeometryImpl : IGeometryImpl + { + IStreamGeometryContextImpl Open(); + } +} diff --git a/Perspex.SceneGraph/Platform/ITextService.cs b/Perspex.SceneGraph/Platform/ITextService.cs new file mode 100644 index 0000000000..d4eadd7b91 --- /dev/null +++ b/Perspex.SceneGraph/Platform/ITextService.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + using Perspex.Media; + + public interface ITextService + { + int GetCaretIndex(FormattedText text, Point point, Size constraint); + + Point GetCaretPosition(FormattedText text, int caretIndex, Size constraint); + + double[] GetLineHeights(FormattedText text, Size constraint); + + Size Measure(FormattedText text, Size constraint); + } +} diff --git a/Perspex.SceneGraph/Point.cs b/Perspex.SceneGraph/Point.cs new file mode 100644 index 0000000000..8e000e9d34 --- /dev/null +++ b/Perspex.SceneGraph/Point.cs @@ -0,0 +1,77 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System.Globalization; + + /// + /// Defines a point. + /// + public struct Point + { + /// + /// The X position. + /// + private double x; + + /// + /// The Y position. + /// + private double y; + + /// + /// Initializes a new instance of the structure. + /// + /// The X position. + /// The Y position. + public Point(double x, double y) + { + this.x = x; + this.y = y; + } + + /// + /// Gets the X position. + /// + public double X + { + get { return this.x; } + } + + /// + /// Gets the Y position. + /// + public double Y + { + get { return this.y; } + } + + public static Point operator +(Point a, Point b) + { + return new Point(a.x + b.x, a.y + b.y); + } + + public static Point operator -(Point a, Point b) + { + return new Point(a.x - b.x, a.y - b.y); + } + + public static implicit operator Vector(Point p) + { + return new Vector(p.x, p.y); + } + + /// + /// Returns the string representation of the point. + /// + /// The string representation of the point. + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", this.x, this.y); + } + } +} diff --git a/Perspex.SceneGraph/Properties/AssemblyInfo.cs b/Perspex.SceneGraph/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..166873c93d --- /dev/null +++ b/Perspex.SceneGraph/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.SceneGraph")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.SceneGraph")] +[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")] diff --git a/Perspex.SceneGraph/Readme.txt b/Perspex.SceneGraph/Readme.txt new file mode 100644 index 0000000000..88acdb41a2 --- /dev/null +++ b/Perspex.SceneGraph/Readme.txt @@ -0,0 +1,16 @@ +Portable libraries built using Rx do not include the System.Reactive.PlatformServices assembly +containing "platform enlightenments" that depend on platform-specific functionality. (Doing so +would prevent the library to be portable due to the dependencies on a specific platform.) + +When including the resulting portable library in another project, please include the platform's +System.Reactive.PlatformServices assembly in order to get the best performance. To include this +assembly, use any of the following options: + +1. Select the System.Reactive.PlatformServices assembly from the Visual Studio "Add Reference" + dialog. This option works for Windows Store apps, .NET 4.5, and Windows Phone 8 projects. + +2. For Windows Store apps and Windows Phone 8 projects, use the Reactive Extensions Extension SDK + which can be found in the "Add Reference" dialog. + +3. Use NuGet to include the Rx-Main package (or any package that depends on Rx-Main, such as + Rx-Xaml) which will automatically include the Rx-PlatformServices enlightenment package. diff --git a/Perspex.SceneGraph/Rect.cs b/Perspex.SceneGraph/Rect.cs new file mode 100644 index 0000000000..88f8abfe6b --- /dev/null +++ b/Perspex.SceneGraph/Rect.cs @@ -0,0 +1,300 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Globalization; + + /// + /// Defines a rectangle. + /// + public struct Rect + { + /// + /// The X position. + /// + private double x; + + /// + /// The Y position. + /// + private double y; + + /// + /// The width. + /// + private double width; + + /// + /// The height. + /// + private double height; + + /// + /// Initializes a new instance of the structure. + /// + /// The X position. + /// The Y position. + /// The width. + /// The height. + public Rect(double x, double y, double width, double height) + { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The size of the rectangle. + public Rect(Size size) + { + this.x = 0; + this.y = 0; + this.width = size.Width; + this.height = size.Height; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The position of the rectangle. + /// The size of the rectangle. + public Rect(Point position, Size size) + { + this.x = position.X; + this.y = position.Y; + this.width = size.Width; + this.height = size.Height; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The top left position of the rectangle. + /// The bottom right position of the rectangle. + public Rect(Point topLeft, Point bottomRight) + { + this.x = topLeft.X; + this.y = topLeft.Y; + this.width = bottomRight.X - topLeft.X; + this.height = bottomRight.Y - topLeft.Y; + } + + /// + /// Gets the X position. + /// + public double X + { + get { return this.x; } + } + + /// + /// Gets the Y position. + /// + public double Y + { + get { return this.y; } + } + + /// + /// Gets the width. + /// + public double Width + { + get { return this.width; } + } + + /// + /// Gets the height. + /// + public double Height + { + get { return this.height; } + } + + /// + /// Gets the position of the rectangle. + /// + public Point Position + { + get { return new Point(this.x, this.y); } + } + + /// + /// Gets the size of the rectangle. + /// + public Size Size + { + get { return new Size(this.width, this.height); } + } + + /// + /// Gets the right position of the rectangle. + /// + public double Right + { + get { return this.x + this.width; } + } + + /// + /// Gets the bottom position of the rectangle. + /// + public double Bottom + { + get { return this.y + this.height; } + } + + /// + /// Gets the top left point of the rectangle. + /// + public Point TopLeft + { + get { return new Point(this.x, this.y); } + } + + /// + /// Gets the top right point of the rectangle. + /// + public Point TopRight + { + get { return new Point(this.Right, this.y); } + } + + /// + /// Gets the bottom left point of the rectangle. + /// + public Point BottomLeft + { + get { return new Point(this.x, this.Bottom); } + } + + /// + /// Gets the bottom right point of the rectangle. + /// + public Point BottomRight + { + get { return new Point(this.Right, this.Bottom); } + } + + /// + /// Gets a value that indicates whether the rectangle is empty. + /// + public bool IsEmpty + { + get { return this.width == 0 && this.height == 0; } + } + + public static Rect operator *(Rect rect, Vector scale) + { + double centerX = rect.x + rect.width / 2; + double centerY = rect.y + rect.height / 2; + double width = rect.width * scale.X; + double height = rect.height * scale.Y; + return new Rect( + centerX - (width / 2), + centerY - (height / 2), + width, + height); + } + + public static Rect operator /(Rect rect, Vector scale) + { + double centerX = rect.x + rect.width / 2; + double centerY = rect.y + rect.height / 2; + double width = rect.width / scale.X; + double height = rect.height / scale.Y; + return new Rect( + centerX - (width / 2), + centerY - (height / 2), + width, + height); + } + + /// + /// Determines whether a points in in the bounds of the rectangle. + /// + /// The point. + /// true if the point is in the bounds of the rectangle; otherwise false. + public bool Contains(Point p) + { + return p.X >= this.x && p.X < this.x + this.width && + p.Y >= this.y && p.Y < this.y + this.height; + } + + public Rect Center(Rect rect) + { + return new Rect( + this.x + ((this.width - rect.width) / 2), + this.y + ((this.height - rect.height) / 2), + rect.width, + rect.height); + } + + /// + /// Deflates the rectangle. + /// + /// The thickness. + /// The deflated rectangle. + /// The deflated rectangle size cannot be less than 0. + public Rect Deflate(double thickness) + { + return this.Deflate(new Thickness(thickness)); + } + + /// + /// Deflates the rectangle by a . + /// + /// The thickness. + /// The deflated rectangle. + /// The deflated rectangle size cannot be less than 0. + public Rect Deflate(Thickness thickness) + { + return new Rect( + new Point(this.x + thickness.Left, this.y + thickness.Top), + this.Size.Deflate(thickness)); + } + + public Rect Intersect(Rect rect) + { + double x = Math.Max(this.x, rect.x); + double y = Math.Max(this.y, rect.y); + double width = Math.Min(this.Right, rect.Right) - x; + double height = Math.Min(this.Bottom, rect.Bottom) - y; + + if (width < 0 || height < 0) + { + return new Rect( + double.PositiveInfinity, + double.PositiveInfinity, + double.NegativeInfinity, + double.NegativeInfinity); + } + else + { + return new Rect(x, y, width, height); + } + } + + /// + /// Returns the string representation of the rectangle. + /// + /// The string representation of the rectangle. + public override string ToString() + { + return string.Format( + CultureInfo.InvariantCulture, + "{0}, {1}, {2}, {3}", + this.x, + this.y, + this.width, + this.height); + } + } +} diff --git a/Perspex.SceneGraph/Rendering/IRenderManager.cs b/Perspex.SceneGraph/Rendering/IRenderManager.cs new file mode 100644 index 0000000000..f239f7d7e5 --- /dev/null +++ b/Perspex.SceneGraph/Rendering/IRenderManager.cs @@ -0,0 +1,18 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Rendering +{ + using System; + using System.Reactive; + + public interface IRenderManager + { + IObservable RenderNeeded { get; } + + void InvalidateRender(IVisual visual); + } +} diff --git a/Perspex.SceneGraph/Rendering/IRenderRoot.cs b/Perspex.SceneGraph/Rendering/IRenderRoot.cs new file mode 100644 index 0000000000..154e8d0b3c --- /dev/null +++ b/Perspex.SceneGraph/Rendering/IRenderRoot.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Rendering +{ + public interface IRenderRoot + { + IRenderManager RenderManager { get; } + } +} diff --git a/Perspex.SceneGraph/Rendering/RenderManager.cs b/Perspex.SceneGraph/Rendering/RenderManager.cs new file mode 100644 index 0000000000..081b570299 --- /dev/null +++ b/Perspex.SceneGraph/Rendering/RenderManager.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Rendering +{ + using System; + using System.Reactive; + using System.Reactive.Subjects; + + public class RenderManager : IRenderManager + { + private Subject renderNeeded = new Subject(); + + private bool renderQueued; + + public IObservable RenderNeeded + { + get { return this.renderNeeded; } + } + + public bool RenderQueued + { + get { return this.renderQueued; } + } + + public void InvalidateRender(IVisual visual) + { + if (!this.renderQueued) + { + this.renderNeeded.OnNext(Unit.Default); + this.renderQueued = true; + } + } + + public void RenderFinished() + { + this.renderQueued = false; + } + } +} diff --git a/Perspex.SceneGraph/Size.cs b/Perspex.SceneGraph/Size.cs new file mode 100644 index 0000000000..23e4cab500 --- /dev/null +++ b/Perspex.SceneGraph/Size.cs @@ -0,0 +1,164 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Globalization; + + /// + /// Defines a size. + /// + public struct Size + { + /// + /// The width. + /// + private double width; + + /// + /// The height. + /// + private double height; + + /// + /// Initializes a new instance of the structure. + /// + /// The width. + /// The height. + public Size(double width, double height) + { + this.width = width; + this.height = height; + } + + /// + /// Gets the width. + /// + public double Width + { + get { return this.width; } + } + + /// + /// Gets the height. + /// + public double Height + { + get { return this.height; } + } + + /// + /// Checks for equality between two s. + /// + /// The first size. + /// The second size. + /// True if the sizes are equal; otherwise false. + public static bool operator ==(Size left, Size right) + { + return left.width == right.width && left.height == right.height; + } + + /// + /// Checks for unequality between two s. + /// + /// The first size. + /// The second size. + /// True if the sizes are unequal; otherwise false. + public static bool operator !=(Size left, Size right) + { + return !(left == right); + } + + /// + /// Scales a size. + /// + /// The size + /// The scaling factor. + /// The scaled size. + public static Size operator *(Size size, Vector scale) + { + return new Size(size.width * scale.X, size.height * scale.Y); + } + + /// + /// Scales a size. + /// + /// The size + /// The scaling factor. + /// The scaled size. + public static Size operator /(Size size, Vector scale) + { + return new Size(size.width / scale.X, size.height / scale.Y); + } + + /// + /// Constrains the size. + /// + /// The size to constrain to. + /// The constrained size. + public Size Constrain(Size constraint) + { + return new Size( + Math.Min(this.width, constraint.width), + Math.Min(this.height, constraint.height)); + } + + /// + /// Deflates the size by a . + /// + /// The thickness. + /// The deflated size. + /// The deflated size cannot be less than 0. + public Size Deflate(Thickness thickness) + { + return new Size( + Math.Max(0, this.width - thickness.Left - thickness.Right), + Math.Max(0, this.height - thickness.Top - thickness.Bottom)); + } + + /// + /// Inflates the size by a . + /// + /// The thickness. + /// The inflated size. + public Size Inflate(Thickness thickness) + { + return new Size( + this.width + thickness.Left + thickness.Right, + this.height + thickness.Top + thickness.Bottom); + } + + /// + /// Returns a new with the same height and the specified width. + /// + /// The width. + /// The new . + public Size WithWidth(double width) + { + return new Size(width, this.height); + } + + /// + /// Returns a new with the same width and the specified height. + /// + /// The height. + /// The new . + public Size WithHeight(double height) + { + return new Size(this.width, height); + } + + /// + /// Returns the string representation of the size. + /// + /// The string representation of the size + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", this.width, this.height); + } + } +} diff --git a/Perspex.SceneGraph/Thickness.cs b/Perspex.SceneGraph/Thickness.cs new file mode 100644 index 0000000000..65f01fefc2 --- /dev/null +++ b/Perspex.SceneGraph/Thickness.cs @@ -0,0 +1,182 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + + public struct Thickness + { + /// + /// The thickness on the left. + /// + private double left; + + /// + /// The thickness on the top. + /// + private double top; + + /// + /// The thickness on the right. + /// + private double right; + + /// + /// The thickness on the bottom. + /// + private double bottom; + + /// + /// Initializes a new instance of the structure. + /// + /// The length that should be applied to all sides. + public Thickness(double uniformLength) + { + Contract.Requires(uniformLength >= 0); + + this.left = this.top = this.right = this.bottom = uniformLength; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The thickness on the left and right. + /// The thickness on the top and bottom. + public Thickness(double horizontal, double vertical) + { + Contract.Requires(horizontal >= 0); + Contract.Requires(vertical >= 0); + + this.left = this.right = horizontal; + this.top = this.bottom = vertical; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The thickness on the left. + /// The thickness on the top. + /// The thickness on the right. + /// The thickness on the bottom. + public Thickness(double left, double top, double right, double bottom) + { + Contract.Requires(left >= 0); + Contract.Requires(top >= 0); + Contract.Requires(right >= 0); + Contract.Requires(bottom >= 0); + + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + + /// + /// Gets the thickness on the left. + /// + public double Left + { + get { return this.left; } + } + + /// + /// Gets the thickness on the top. + /// + public double Top + { + get { return this.top; } + } + + /// + /// Gets the thickness on the right. + /// + public double Right + { + get { return this.right; } + } + + /// + /// Gets the thickness on the bottom. + /// + public double Bottom + { + get { return this.bottom; } + } + + /// + /// Gets a value indicating whether all sides are set to 0. + /// + public bool IsEmpty + { + get { return this.Left == 0 && this.Top == 0 && this.Right == 0 && this.Bottom == 0; } + } + + /// + /// Compares two Thicknesses. + /// + /// The first thickness. + /// The second thickness. + /// The equality. + public static bool operator ==(Thickness a, Thickness b) + { + return a.Equals(b); + } + + /// + /// Compares two Thicknesses. + /// + /// The first thickness. + /// The second thickness. + /// The unequality. + public static bool operator !=(Thickness a, Thickness b) + { + return !a.Equals(b); + } + + /// + /// Adds two Thicknesses. + /// + /// The first thickness. + /// The second thickness. + /// The equality. + public static Thickness operator +(Thickness a, Thickness b) + { + return new Thickness( + a.Left + b.Left, + a.Top + b.Top, + a.Right + b.Right, + a.Bottom + b.Bottom); + } + + public override bool Equals(object obj) + { + if (obj is Thickness) + { + Thickness other = (Thickness)obj; + return this.Left == other.Left && + this.Top == other.Top && + this.Right == other.Right && + this.Bottom == other.Bottom; + } + + return false; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = (hash * 23) + this.Left.GetHashCode(); + hash = (hash * 23) + this.Top.GetHashCode(); + hash = (hash * 23) + this.Right.GetHashCode(); + hash = (hash * 23) + this.Bottom.GetHashCode(); + return hash; + } + } + } +} diff --git a/Perspex.SceneGraph/Vector.cs b/Perspex.SceneGraph/Vector.cs new file mode 100644 index 0000000000..7222ef7c51 --- /dev/null +++ b/Perspex.SceneGraph/Vector.cs @@ -0,0 +1,77 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System.Globalization; + + /// + /// Defines a vector. + /// + public struct Vector + { + /// + /// The X vector. + /// + private double x; + + /// + /// The Y vector. + /// + private double y; + + /// + /// Initializes a new instance of the structure. + /// + /// The X vector. + /// The Y vector. + public Vector(double x, double y) + { + this.x = x; + this.y = y; + } + + /// + /// Gets the X vector. + /// + public double X + { + get { return this.x; } + } + + /// + /// Gets the Y vector. + /// + public double Y + { + get { return this.y; } + } + + public static Vector operator -(Vector a) + { + return new Vector(-a.x, -a.y); + } + + public static Vector operator +(Vector a, Vector b) + { + return new Vector(a.x + b.x, a.y + b.y); + } + + public static Vector operator -(Vector a, Vector b) + { + return new Vector(a.x - b.x, a.y - b.y); + } + + /// + /// Returns the string representation of the point. + /// + /// The string representation of the point. + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", this.x, this.y); + } + } +} diff --git a/Perspex.SceneGraph/Visual.cs b/Perspex.SceneGraph/Visual.cs new file mode 100644 index 0000000000..ebd4c39c11 --- /dev/null +++ b/Perspex.SceneGraph/Visual.cs @@ -0,0 +1,212 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Linq; + using Perspex.Media; + using Perspex.Rendering; + using Splat; + + public abstract class Visual : PerspexObject, IVisual + { + public static readonly PerspexProperty IsVisibleProperty = + PerspexProperty.Register("IsVisible", true); + + public static readonly PerspexProperty OpacityProperty = + PerspexProperty.Register("Opacity", 1); + + public static readonly PerspexProperty RenderTransformProperty = + PerspexProperty.Register("RenderTransform"); + + public static readonly PerspexProperty TransformOriginProperty = + PerspexProperty.Register("TransformOrigin", defaultValue: Origin.Default); + + private Rect bounds; + + private PerspexList visualChildren; + + private IVisual visualParent; + + static Visual() + { + AffectsRender(IsVisibleProperty); + } + + public bool IsVisible + { + get { return this.GetValue(IsVisibleProperty); } + set { this.SetValue(IsVisibleProperty, value); } + } + + public double Opacity + { + get { return this.GetValue(OpacityProperty); } + set { this.SetValue(OpacityProperty, value); } + } + + public ITransform RenderTransform + { + get { return this.GetValue(RenderTransformProperty); } + set { this.SetValue(RenderTransformProperty, value); } + } + + public Origin TransformOrigin + { + get { return this.GetValue(TransformOriginProperty); } + set { this.SetValue(TransformOriginProperty, value); } + } + + Rect IVisual.Bounds + { + get { return this.bounds; } + } + + IEnumerable IVisual.VisualChildren + { + get + { + this.EnsureVisualChildrenCreated(); + return this.visualChildren; + } + } + + IVisual IVisual.VisualParent + { + get { return this.visualParent; } + } + + public void InvalidateVisual() + { + IRenderRoot root = this.GetSelfAndVisualAncestors() + .OfType() + .FirstOrDefault(); + + if (root != null && root.RenderManager != null) + { + root.RenderManager.InvalidateRender(this); + } + } + + public virtual void Render(IDrawingContext context) + { + Contract.Requires(context != null); + } + + protected static void AffectsRender(PerspexProperty property) + { + property.Changed.Subscribe(AffectsRenderInvalidate); + } + + protected void AddVisualChild(Visual visual) + { + this.EnsureVisualChildrenCreated(); + this.visualChildren.Add(visual); + } + + protected void AddVisualChildren(IEnumerable visuals) + { + this.EnsureVisualChildrenCreated(); + this.visualChildren.AddRange(visuals); + } + + protected void ClearVisualChildren() + { + this.EnsureVisualChildrenCreated(); + this.visualChildren.Clear(); + } + + protected void RemoveVisualChild(Visual visual) + { + this.EnsureVisualChildrenCreated(); + this.visualChildren.Remove(visual); + } + + protected void SetVisualBounds(Rect bounds) + { + this.bounds = bounds; + } + + protected virtual IEnumerable CreateVisualChildren() + { + return Enumerable.Empty(); + } + + protected virtual void OnAttachedToVisualTree(IRenderRoot root) + { + } + + protected virtual void OnDetachedFromVisualTree(IRenderRoot oldRoot) + { + } + + protected virtual void OnVisualParentChanged(IVisual oldParent) + { + } + + private static void AffectsRenderInvalidate(PerspexPropertyChangedEventArgs e) + { + Visual visual = e.Sender as Visual; + + if (visual != null) + { + visual.InvalidateVisual(); + } + } + + private void EnsureVisualChildrenCreated() + { + if (this.visualChildren == null) + { + this.visualChildren = new PerspexList(this.CreateVisualChildren()); + this.visualChildren.CollectionChanged += VisualChildrenChanged; + } + } + + private void VisualChildrenChanged(object sender, NotifyCollectionChangedEventArgs e) + { + } + + private void NotifyAttachedToVisualTree(IRenderRoot root) + { + this.Log().Debug(string.Format( + "Attached {0} (#{1:x8}) to visual tree", + this.GetType().Name, + this.GetHashCode())); + + this.OnAttachedToVisualTree(root); + + if (this.visualChildren != null) + { + foreach (Visual child in this.visualChildren.OfType()) + { + child.NotifyAttachedToVisualTree(root); + } + } + } + + private void NotifyDetachedFromVisualTree(IRenderRoot oldRoot) + { + this.Log().Debug(string.Format( + "Detached {0} (#{1:x8}) from visual tree", + this.GetType().Name, + this.GetHashCode())); + + this.OnDetachedFromVisualTree(oldRoot); + + if (this.visualChildren != null) + { + foreach (Visual child in this.visualChildren.OfType()) + { + child.NotifyDetachedFromVisualTree(oldRoot); + } + } + } + } +} diff --git a/Perspex.SceneGraph/VisualExtensions.cs b/Perspex.SceneGraph/VisualExtensions.cs new file mode 100644 index 0000000000..d7452ed012 --- /dev/null +++ b/Perspex.SceneGraph/VisualExtensions.cs @@ -0,0 +1,109 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Collections.Generic; + using System.Linq; + + public static class VisualExtensions + { + public static IEnumerable GetVisualAncestors(this IVisual visual) + { + Contract.Requires(visual != null); + + visual = visual.VisualParent; + + while (visual != null) + { + yield return visual; + visual = visual.VisualParent; + } + } + + public static IEnumerable GetSelfAndVisualAncestors(this IVisual visual) + { + yield return visual; + + foreach (var ancestor in visual.GetVisualAncestors()) + { + yield return ancestor; + } + } + + public static IVisual GetVisualAt(this IVisual visual, Point p) + { + Contract.Requires(visual != null); + + return visual.GetVisualsAt(p).FirstOrDefault(); + } + + public static IEnumerable GetVisualsAt(this IVisual visual, Point p) + { + Contract.Requires(visual != null); + + if (visual.Bounds.Contains(p)) + { + p -= visual.Bounds.Position; + + if (visual.VisualChildren.Any()) + { + foreach (IVisual child in visual.VisualChildren) + { + foreach (IVisual v in child.GetVisualsAt(p)) + { + yield return v; + } + } + } + + yield return visual; + } + } + + public static IEnumerable GetVisualChildren(this IVisual visual) + { + return visual.VisualChildren; + } + + public static IEnumerable GetVisualDescendents(this IVisual visual) + { + foreach (IVisual child in visual.VisualChildren) + { + yield return child; + + foreach (IVisual descendent in child.GetVisualDescendents()) + { + yield return descendent; + } + } + } + + public static IVisual GetVisualParent(this IVisual visual) + { + return visual.VisualParent; + } + + public static T GetVisualParent(this IVisual visual) where T : class + { + return visual.VisualParent as T; + } + + public static IEnumerable GetVisualSiblings(this IVisual visual) + { + IVisual parent = visual.VisualParent; + + if (parent != null) + { + foreach (IVisual sibling in parent.VisualChildren) + { + yield return sibling; + } + } + } + } +} diff --git a/Perspex.SceneGraph/packages.config b/Perspex.SceneGraph/packages.config new file mode 100644 index 0000000000..af8b5868bf --- /dev/null +++ b/Perspex.SceneGraph/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj new file mode 100644 index 0000000000..dc00656021 --- /dev/null +++ b/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj @@ -0,0 +1,129 @@ + + + + Debug + AnyCPU + {47ECDF59-DEF8-4C53-87B1-2098A3429059} + Library + Properties + Perspex.Styling.UnitTests + Perspex.Styling.UnitTests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Moq.4.2.1312.1622\lib\net40\Moq.dll + + + + ..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll + + + ..\packages\Rx-Interfaces.2.1.30214.0\lib\Net45\System.Reactive.Interfaces.dll + + + ..\packages\Rx-Linq.2.1.30214.0\lib\Net45\System.Reactive.Linq.dll + + + ..\packages\Rx-PlatformServices.2.1.30214.0\lib\Net45\System.Reactive.PlatformServices.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/Perspex.Styling.UnitTests/Properties/AssemblyInfo.cs b/Perspex.Styling.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..e40471be85 --- /dev/null +++ b/Perspex.Styling.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Styling.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Styling.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("32d08514-3400-4083-b1bd-4565b5bc6d0c")] + +// 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")] diff --git a/Perspex.UnitTests/Styling/SelectorTests_Class.cs b/Perspex.Styling.UnitTests/SelectorTests_Class.cs similarity index 98% rename from Perspex.UnitTests/Styling/SelectorTests_Class.cs rename to Perspex.Styling.UnitTests/SelectorTests_Class.cs index 3080b68ed2..e79f3dc4b4 100644 --- a/Perspex.UnitTests/Styling/SelectorTests_Class.cs +++ b/Perspex.Styling.UnitTests/SelectorTests_Class.cs @@ -4,13 +4,12 @@ // // ----------------------------------------------------------------------- -namespace Perspex.UnitTests.Styling +namespace Perspex.Styling.UnitTests { using System.Linq; using System.Reactive.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; - using Perspex.Controls; using Perspex.Styling; [TestClass] diff --git a/Perspex.UnitTests/Styling/SelectorTests_Descendent.cs b/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs similarity index 98% rename from Perspex.UnitTests/Styling/SelectorTests_Descendent.cs rename to Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index dfac59daf8..9d0aa5a920 100644 --- a/Perspex.UnitTests/Styling/SelectorTests_Descendent.cs +++ b/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -4,13 +4,12 @@ // // ----------------------------------------------------------------------- -namespace Perspex.UnitTests.Styling +namespace Perspex.Styling.UnitTests { using System.Linq; using System.Reactive.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; - using Perspex.Controls; using Perspex.Styling; [TestClass] diff --git a/Perspex.UnitTests/Styling/SelectorTests_Id.cs b/Perspex.Styling.UnitTests/SelectorTests_Id.cs similarity index 95% rename from Perspex.UnitTests/Styling/SelectorTests_Id.cs rename to Perspex.Styling.UnitTests/SelectorTests_Id.cs index 65a8d0b574..d2778ee017 100644 --- a/Perspex.UnitTests/Styling/SelectorTests_Id.cs +++ b/Perspex.Styling.UnitTests/SelectorTests_Id.cs @@ -4,15 +4,12 @@ // // ----------------------------------------------------------------------- -namespace Perspex.UnitTests.Styling +namespace Perspex.Styling.UnitTests { - using System; - using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; - using Perspex.Controls; using Perspex.Styling; [TestClass] diff --git a/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs b/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs new file mode 100644 index 0000000000..79400cb351 --- /dev/null +++ b/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Styling.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Perspex.Styling; + + ////[TestClass] + ////public class SelectorTests_Multiple + ////{ + //// [TestMethod] + //// public void Template_Child_Of_Control_With_Two_Classes() + //// { + //// var template = new ControlTemplate(parent => + //// { + //// return new Border + //// { + //// Id = "border", + //// }; + //// }); + + //// var control = new Button + //// { + //// Template = template, + //// }; + + //// var selector = new Selector() + //// .OfType