diff --git a/Perspex.Windows/Window.cs b/Perspex.Windows/Window.cs index f0ac4892c7..beea0502e3 100644 --- a/Perspex.Windows/Window.cs +++ b/Perspex.Windows/Window.cs @@ -9,8 +9,10 @@ namespace Perspex.Windows using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; + using System.Linq; using System.Runtime.InteropServices; using Perspex.Controls; + using Perspex.Input; using Perspex.Layout; using Perspex.Windows.Interop; using Perspex.Windows.Threading; @@ -132,6 +134,26 @@ namespace Perspex.Windows } } + private void MouseDown(Visual visual, Point p) + { + Visual hit = visual.GetVisualAt(p); + + if (hit != null) + { + Interactive source = (hit as Interactive) ?? hit.GetVisualAncestor(); + + if (source != null) + { + source.RaiseEvent(new MouseEventArgs + { + RoutedEvent = Control.MouseLeftButtonDownEvent, + OriginalSource = source, + Source = source, + }); + } + } + } + private void MouseMove(Visual visual, Point p) { Control control = visual as Control; @@ -147,6 +169,26 @@ namespace Perspex.Windows } } + private void MouseUp(Visual visual, Point p) + { + Visual hit = visual.GetVisualAt(p); + + if (hit != null) + { + Interactive source = (hit as Interactive) ?? hit.GetVisualAncestor(); + + if (source != null) + { + source.RaiseEvent(new MouseEventArgs + { + RoutedEvent = Control.MouseLeftButtonUpEvent, + OriginalSource = source, + Source = source, + }); + } + } + } + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { @@ -164,13 +206,13 @@ namespace Perspex.Windows //// KeyInterop.KeyFromVirtualKey((int)wParam))); //// break; - ////case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN: - //// InputManager.Current.ProcessInput(new RawMouseEventArgs(mouse, RawMouseEventType.LeftButtonDown)); - //// break; + case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN: + this.MouseDown(this, new Point((uint)lParam & 0xffff, (uint)lParam >> 16)); + break; - ////case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP: - //// InputManager.Current.ProcessInput(new RawMouseEventArgs(mouse, RawMouseEventType.LeftButtonUp)); - //// break; + case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP: + this.MouseUp(this, new Point((uint)lParam & 0xffff, (uint)lParam >> 16)); + break; case UnmanagedMethods.WindowsMessage.WM_MOUSEMOVE: this.MouseMove(this, new Point((uint)lParam & 0xffff, (uint)lParam >> 16)); diff --git a/Perspex/Controls/Button.cs b/Perspex/Controls/Button.cs index 5806086e70..45dbc9138b 100644 --- a/Perspex/Controls/Button.cs +++ b/Perspex/Controls/Button.cs @@ -10,6 +10,19 @@ namespace Perspex.Controls public class Button : ContentControl { + public Button() + { + this.GetObservable(MouseLeftButtonDownEvent).Subscribe(e => + { + this.Classes.Add(":pressed"); + }); + + this.GetObservable(MouseLeftButtonUpEvent).Subscribe(e => + { + this.Classes.Remove(":pressed"); + }); + } + protected override Visual DefaultTemplate() { Border border = new Border(); diff --git a/Perspex/Controls/Control.cs b/Perspex/Controls/Control.cs index ff9ad7ed45..115ff7d838 100644 --- a/Perspex/Controls/Control.cs +++ b/Perspex/Controls/Control.cs @@ -11,6 +11,7 @@ namespace Perspex.Controls using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.Reactive.Linq; + using Perspex.Input; using Perspex.Layout; using Perspex.Media; @@ -30,7 +31,7 @@ namespace Perspex.Controls Bottom, } - public abstract class Control : Visual, ILayoutable + public abstract class Control : Interactive, ILayoutable { public static readonly ReadOnlyPerspexProperty ParentProperty = new ReadOnlyPerspexProperty(ParentPropertyRW); @@ -59,6 +60,12 @@ namespace Perspex.Controls public static readonly PerspexProperty VerticalAlignmentProperty = PerspexProperty.Register("VerticalAlignment"); + public static readonly RoutedEvent MouseLeftButtonDownEvent = + RoutedEvent.Register("MouseLeftButtonDown", RoutingStrategy.Bubble); + + public static readonly RoutedEvent MouseLeftButtonUpEvent = + RoutedEvent.Register("MouseLeftButtonUp", RoutingStrategy.Bubble); + internal static readonly PerspexProperty ParentPropertyRW = PerspexProperty.Register("Parent"); @@ -85,6 +92,35 @@ namespace Perspex.Controls this.GetObservable(BackgroundProperty).Skip(1).Subscribe(_ => this.InvalidateMeasure()); } + public event EventHandler MouseLeftButtonDown + { + add + { + Contract.Requires(value != null); + this.AddHandler(MouseLeftButtonDownEvent, value); + } + + remove + { + Contract.Requires(value != null); + this.RemoveHandler(MouseLeftButtonDownEvent, value); + } + } + + public event EventHandler MouseLeftButtonUp + { + add + { + Contract.Requires(value != null); + this.AddHandler(MouseLeftButtonUpEvent, value); + } + remove + { + Contract.Requires(value != null); + this.RemoveHandler(MouseLeftButtonUpEvent, value); + } + } + public Brush Background { get { return this.GetValue(BackgroundProperty); } diff --git a/Perspex/Input/MouseEventArgs.cs b/Perspex/Input/MouseEventArgs.cs new file mode 100644 index 0000000000..4ae30ad110 --- /dev/null +++ b/Perspex/Input/MouseEventArgs.cs @@ -0,0 +1,14 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + using System; + + public class MouseEventArgs : RoutedEventArgs + { + } +} diff --git a/Perspex/Interactive.cs b/Perspex/Interactive.cs new file mode 100644 index 0000000000..c2e4dd657c --- /dev/null +++ b/Perspex/Interactive.cs @@ -0,0 +1,106 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Reactive; + using System.Reactive.Linq; + + public class Interactive : Visual + { + 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; + default: + throw new NotImplementedException(); + } + } + } + + private void BubbleEvent(RoutedEventArgs e) + { + Contract.Requires(e != null); + + Interactive target = this; + + while (target != null) + { + target.RaiseEventImpl(e); + target = target.GetVisualAncestor(); + } + } + + private void RaiseEventImpl(RoutedEventArgs e) + { + Contract.Requires(e != null); + + List delegates; + + if (this.eventHandlers.TryGetValue(e.RoutedEvent, out delegates)) + { + foreach (Delegate handler in delegates) + { + // TODO: Implement the Handled stuff. + handler.DynamicInvoke(this, e); + } + } + } + } +} diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj index c191f1bc6a..1fa7d0b0cb 100644 --- a/Perspex/Perspex.csproj +++ b/Perspex/Perspex.csproj @@ -76,6 +76,8 @@ + + @@ -90,6 +92,7 @@ + @@ -97,6 +100,7 @@ + @@ -110,6 +114,7 @@ + diff --git a/Perspex/RoutedEvent.cs b/Perspex/RoutedEvent.cs new file mode 100644 index 0000000000..d846484f9b --- /dev/null +++ b/Perspex/RoutedEvent.cs @@ -0,0 +1,87 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Diagnostics.Contracts; + 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 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/RoutedEventArgs.cs b/Perspex/RoutedEventArgs.cs new file mode 100644 index 0000000000..acafd65a7e --- /dev/null +++ b/Perspex/RoutedEventArgs.cs @@ -0,0 +1,36 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + + public class RoutedEventArgs : EventArgs + { + public RoutedEventArgs() + { + } + + public RoutedEventArgs(RoutedEvent routedEvent) + { + this.RoutedEvent = routedEvent; + } + + public RoutedEventArgs(RoutedEvent routedEvent, object source) + { + this.RoutedEvent = routedEvent; + this.Source = source; + } + + public bool Handled { get; set; } + + public object OriginalSource { get; set; } + + public RoutedEvent RoutedEvent { get; set; } + + public object Source { get; set; } + } +} diff --git a/Perspex/Themes/Default/ButtonStyle.cs b/Perspex/Themes/Default/ButtonStyle.cs index 7cb4cdd6e8..32d1f600dd 100644 --- a/Perspex/Themes/Default/ButtonStyle.cs +++ b/Perspex/Themes/Default/ButtonStyle.cs @@ -31,7 +31,15 @@ namespace Perspex.Themes.Default new Setter (Button.BackgroundProperty, new SolidColorBrush(0xffbee6fd)), new Setter (Button.BorderBrushProperty, new SolidColorBrush(0xff3c7fb1)), }, - } + }, + new Style(x => x.Select