diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj
index 6547b5bf99..2cb1dd0369 100644
--- a/Perspex.Base/Perspex.Base.csproj
+++ b/Perspex.Base/Perspex.Base.csproj
@@ -37,6 +37,7 @@
+
diff --git a/Perspex.Base/PerspexListExtensions.cs b/Perspex.Base/PerspexListExtensions.cs
new file mode 100644
index 0000000000..c62a8bfad0
--- /dev/null
+++ b/Perspex.Base/PerspexListExtensions.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Reactive.Disposables;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Perspex
+{
+ public static class PerspexListExtensions
+ {
+ public static IDisposable ForEachItem(
+ this IReadOnlyPerspexList collection,
+ Action added,
+ Action removed)
+ {
+ NotifyCollectionChangedEventHandler handler = (_, e) =>
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ foreach (T i in e.NewItems)
+ {
+ added(i);
+ }
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ foreach (T i in e.OldItems)
+ {
+ removed(i);
+ }
+
+ foreach (T i in e.NewItems)
+ {
+ added(i);
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ foreach (T i in e.OldItems)
+ {
+ removed(i);
+ }
+ break;
+ }
+ };
+
+ foreach (T i in collection)
+ {
+ added(i);
+ }
+
+ collection.CollectionChanged += handler;
+
+ System.Diagnostics.Debug.WriteLine("Tracked " + collection.GetHashCode());
+
+ return Disposable.Create(() => collection.CollectionChanged -= handler);
+ }
+
+ public static IDisposable TrackItemPropertyChanged(
+ this IReadOnlyPerspexList collection,
+ Action> callback)
+ {
+ List tracked = new List();
+
+ PropertyChangedEventHandler handler = (s, e) =>
+ {
+ callback(Tuple.Create(s, e));
+ };
+
+ collection.ForEachItem(
+ x =>
+ {
+ var inpc = x as INotifyPropertyChanged;
+
+ if (inpc != null)
+ {
+ inpc.PropertyChanged += handler;
+ tracked.Add(inpc);
+ }
+ },
+ x =>
+ {
+ var inpc = x as INotifyPropertyChanged;
+
+ if (inpc != null)
+ {
+ inpc.PropertyChanged -= handler;
+ tracked.Remove(inpc);
+ }
+ });
+
+ return Disposable.Create(() =>
+ {
+ foreach (var i in tracked)
+ {
+ i.PropertyChanged -= handler;
+ }
+ });
+ }
+ }
+}
diff --git a/Perspex.Base/PerspexObject.cs b/Perspex.Base/PerspexObject.cs
index c2d0387763..b5febbdbdd 100644
--- a/Perspex.Base/PerspexObject.cs
+++ b/Perspex.Base/PerspexObject.cs
@@ -8,6 +8,7 @@ namespace Perspex
{
using System;
using System.Collections.Generic;
+ using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
@@ -56,7 +57,7 @@ namespace Perspex
///
/// This class is analogous to DependencyObject in WPF.
///
- public class PerspexObject : IEnableLogger
+ public class PerspexObject : INotifyPropertyChanged, IEnableLogger
{
///
/// The registered properties by type.
@@ -76,10 +77,24 @@ namespace Perspex
new Dictionary();
///
- /// Raised when a value changes on this object/
+ /// Event handler for implementation.
+ ///
+ private PropertyChangedEventHandler inpcChanged;
+
+ ///
+ /// Raised when a value changes on this object.
///
public event EventHandler PropertyChanged;
+ ///
+ /// Raised when a value changes on this object.
+ ///
+ event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
+ {
+ add { this.inpcChanged += value; }
+ remove { this.inpcChanged -= value; }
+ }
+
///
/// Gets or sets the parent object that inherited values
/// are inherited from.
@@ -646,6 +661,12 @@ namespace Perspex
property.NotifyChanged(e);
this.PropertyChanged(this, e);
}
+
+ if (this.inpcChanged != null)
+ {
+ PropertyChangedEventArgs e = new PropertyChangedEventArgs(property.Name);
+ this.inpcChanged(this, e);
+ }
}
}
}
diff --git a/Perspex.Controls/Button.cs b/Perspex.Controls/Button.cs
index 95462d27fc..c6ef99954f 100644
--- a/Perspex.Controls/Button.cs
+++ b/Perspex.Controls/Button.cs
@@ -34,7 +34,10 @@ namespace Perspex.Controls
if (this.Classes.Contains(":pointerover"))
{
- RoutedEventArgs click = new RoutedEventArgs(ClickEvent, this);
+ RoutedEventArgs click = new RoutedEventArgs
+ {
+ RoutedEvent = ClickEvent,
+ };
this.RaiseEvent(click);
}
};
diff --git a/Perspex.Controls/Grid.cs b/Perspex.Controls/Grid.cs
index 4cc626b139..617cc50982 100644
--- a/Perspex.Controls/Grid.cs
+++ b/Perspex.Controls/Grid.cs
@@ -27,25 +27,64 @@ namespace Perspex.Controls
public static readonly PerspexProperty RowSpanProperty =
PerspexProperty.RegisterAttached("RowSpan", 1);
+ private ColumnDefinitions columnDefinitions;
+
+ private RowDefinitions rowDefinitions;
+
private Segment[,] rowMatrix;
+
private Segment[,] colMatrix;
public Grid()
{
- this.ColumnDefinitions = new ColumnDefinitions();
- this.RowDefinitions = new RowDefinitions();
}
public ColumnDefinitions ColumnDefinitions
{
- get;
- set;
+ get
+ {
+ if (this.columnDefinitions == null)
+ {
+ this.ColumnDefinitions = new ColumnDefinitions();
+ }
+
+ return this.columnDefinitions;
+ }
+
+ set
+ {
+ if (this.columnDefinitions != null)
+ {
+ throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented.");
+ }
+
+ this.columnDefinitions = value;
+ this.columnDefinitions.TrackItemPropertyChanged(_ => this.InvalidateMeasure());
+ }
}
public RowDefinitions RowDefinitions
{
- get;
- set;
+ get
+ {
+ if (this.rowDefinitions == null)
+ {
+ this.RowDefinitions = new RowDefinitions();
+ }
+
+ return this.rowDefinitions;
+ }
+
+ set
+ {
+ if (this.rowDefinitions != null)
+ {
+ throw new NotSupportedException("Reassigning RowDefinitions not yet implemented.");
+ }
+
+ this.rowDefinitions = value;
+ this.rowDefinitions.TrackItemPropertyChanged(_ => this.InvalidateMeasure());
+ }
}
public static int GetColumn(PerspexObject element)
diff --git a/Perspex.Controls/GridSplitter.cs b/Perspex.Controls/GridSplitter.cs
new file mode 100644
index 0000000000..93d62c346f
--- /dev/null
+++ b/Perspex.Controls/GridSplitter.cs
@@ -0,0 +1,34 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls
+{
+ using System;
+ using Perspex.Controls.Primitives;
+ using Perspex.Input;
+
+ public class GridSplitter : Thumb
+ {
+ private Grid grid;
+
+ protected override void OnDragDelta(VectorEventArgs e)
+ {
+ int col = this.GetValue(Grid.ColumnProperty);
+
+ if (grid != null && col > 0)
+ {
+ grid.ColumnDefinitions[col - 1].Width = new GridLength(
+ grid.ColumnDefinitions[col - 1].ActualWidth + e.Vector.X,
+ GridUnitType.Pixel);
+ }
+ }
+
+ protected override void OnVisualParentChanged(Visual oldParent)
+ {
+ this.grid = this.GetVisualParent();
+ }
+ }
+}
diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj
index ce2217bd1e..55b51c2ec4 100644
--- a/Perspex.Controls/Perspex.Controls.csproj
+++ b/Perspex.Controls/Perspex.Controls.csproj
@@ -40,6 +40,7 @@
+
@@ -65,6 +66,7 @@
+
diff --git a/Perspex.Controls/Primitives/Thumb.cs b/Perspex.Controls/Primitives/Thumb.cs
new file mode 100644
index 0000000000..5c9adfcb26
--- /dev/null
+++ b/Perspex.Controls/Primitives/Thumb.cs
@@ -0,0 +1,108 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls.Primitives
+{
+ using System;
+ using Perspex.Input;
+ using Perspex.Interactivity;
+
+ public class Thumb : TemplatedControl
+ {
+ public static readonly RoutedEvent DragStartedEvent =
+ RoutedEvent.Register("DragStarted", RoutingStrategy.Bubble);
+
+ public static readonly RoutedEvent DragDeltaEvent =
+ RoutedEvent.Register("DragDelta", RoutingStrategy.Bubble);
+
+ public static readonly RoutedEvent DragCompletedEvent =
+ RoutedEvent.Register("DragCompleted", RoutingStrategy.Bubble);
+
+ Point? lastPoint;
+
+ public Thumb()
+ {
+ this.DragStarted += (_, e) => this.OnDragStarted(e);
+ this.DragDelta += (_, e) => this.OnDragDelta(e);
+ this.DragCompleted += (_, e) => this.OnDragCompleted(e);
+ }
+
+ public event EventHandler DragStarted
+ {
+ add { this.AddHandler(DragStartedEvent, value); }
+ remove { this.RemoveHandler(DragStartedEvent, value); }
+ }
+
+ public event EventHandler DragDelta
+ {
+ add { this.AddHandler(DragDeltaEvent, value); }
+ remove { this.RemoveHandler(DragDeltaEvent, value); }
+ }
+
+ public event EventHandler DragCompleted
+ {
+ add { this.AddHandler(DragCompletedEvent, value); }
+ remove { this.RemoveHandler(DragCompletedEvent, value); }
+ }
+
+ protected virtual void OnDragStarted(VectorEventArgs e)
+ {
+ }
+
+ protected virtual void OnDragDelta(VectorEventArgs e)
+ {
+ }
+
+ protected virtual void OnDragCompleted(VectorEventArgs e)
+ {
+ }
+
+ protected override void OnPointerMoved(PointerEventArgs e)
+ {
+ if (this.lastPoint.HasValue)
+ {
+ var ev = new VectorEventArgs
+ {
+ RoutedEvent = DragDeltaEvent,
+ Vector = e.GetPosition(this) - this.lastPoint.Value,
+ };
+
+ this.RaiseEvent(ev);
+ }
+ }
+
+ protected override void OnPointerPressed(PointerEventArgs e)
+ {
+ e.Device.Capture(this);
+ this.lastPoint = e.GetPosition(this);
+
+ var ev = new VectorEventArgs
+ {
+ RoutedEvent = DragStartedEvent,
+ Vector = (Vector)this.lastPoint,
+ };
+
+ this.RaiseEvent(ev);
+ }
+
+ protected override void OnPointerReleased(PointerEventArgs e)
+ {
+ if (this.lastPoint.HasValue)
+ {
+ e.Device.Capture(null);
+ this.lastPoint = null;
+
+ var ev = new VectorEventArgs
+ {
+ RoutedEvent = DragCompletedEvent,
+ Vector = (Vector)e.GetPosition(this),
+ };
+
+ this.RaiseEvent(ev);
+ }
+ }
+ }
+}
diff --git a/Perspex.Diagnostics/DevTools.cs b/Perspex.Diagnostics/DevTools.cs
index 682b71753f..17f35f08a2 100644
--- a/Perspex.Diagnostics/DevTools.cs
+++ b/Perspex.Diagnostics/DevTools.cs
@@ -46,7 +46,13 @@ namespace Perspex.Diagnostics
.Where(x => x != null)
.Cast()
.Select(x => new ControlDetails(x.Visual)),
+ [Grid.ColumnProperty] = 2,
+ };
+
+ var splitter = new GridSplitter
+ {
[Grid.ColumnProperty] = 1,
+ Width = 4,
};
this.Content = new Grid
@@ -54,11 +60,13 @@ namespace Perspex.Diagnostics
ColumnDefinitions = new ColumnDefinitions
{
new ColumnDefinition(1, GridUnitType.Star),
+ new ColumnDefinition(4, GridUnitType.Pixel),
new ColumnDefinition(3, GridUnitType.Star),
},
Children = new Controls
{
treeView,
+ splitter,
detailsView,
}
};
diff --git a/Perspex.Input/InputElement.cs b/Perspex.Input/InputElement.cs
index 0fda582268..4c0038aae4 100644
--- a/Perspex.Input/InputElement.cs
+++ b/Perspex.Input/InputElement.cs
@@ -39,6 +39,9 @@ namespace Perspex.Input
public static readonly RoutedEvent PointerLeaveEvent =
RoutedEvent.Register("PointerLeave", RoutingStrategy.Direct);
+ public static readonly RoutedEvent PointerMovedEvent =
+ RoutedEvent.Register("PointerMove", RoutingStrategy.Bubble);
+
public static readonly RoutedEvent PointerPressedEvent =
RoutedEvent.Register("PointerPressed", RoutingStrategy.Bubble);
@@ -53,6 +56,7 @@ namespace Perspex.Input
this.PreviewKeyDown += (_, e) => this.OnPreviewKeyDown(e);
this.PointerEnter += (_, e) => this.OnPointerEnter(e);
this.PointerLeave += (_, e) => this.OnPointerLeave(e);
+ this.PointerMoved += (_, e) => this.OnPointerMoved(e);
this.PointerPressed += (_, e) => this.OnPointerPressed(e);
this.PointerReleased += (_, e) => this.OnPointerReleased(e);
}
@@ -93,6 +97,12 @@ namespace Perspex.Input
remove { this.RemoveHandler(PointerLeaveEvent, value); }
}
+ public event EventHandler PointerMoved
+ {
+ add { this.AddHandler(PointerMovedEvent, value); }
+ remove { this.RemoveHandler(PointerMovedEvent, value); }
+ }
+
public event EventHandler PointerPressed
{
add { this.AddHandler(PointerPressedEvent, value); }
@@ -156,6 +166,10 @@ namespace Perspex.Input
this.IsPointerOver = false;
}
+ protected virtual void OnPointerMoved(PointerEventArgs e)
+ {
+ }
+
protected virtual void OnPointerPressed(PointerEventArgs e)
{
}
diff --git a/Perspex.Input/MouseDevice.cs b/Perspex.Input/MouseDevice.cs
index 89fa0cb8cd..4ef82f543b 100644
--- a/Perspex.Input/MouseDevice.cs
+++ b/Perspex.Input/MouseDevice.cs
@@ -81,9 +81,12 @@ namespace Perspex.Input
private void MouseMove(IMouseDevice device, IVisual visual, Point p)
{
+ IInteractive source;
+
if (this.Captured == null)
{
this.InputManager.SetPointerOver(this, visual, p);
+ source = visual as IInteractive;
}
else
{
@@ -95,6 +98,18 @@ namespace Perspex.Input
}
this.InputManager.SetPointerOver(this, this.Captured, p - offset);
+ source = this.Captured as IInteractive;
+ }
+
+ if (source != null)
+ {
+ source.RaiseEvent(new PointerEventArgs
+ {
+ Device = this,
+ RoutedEvent = InputElement.PointerMovedEvent,
+ OriginalSource = source,
+ Source = source,
+ });
}
}
diff --git a/Perspex.Input/Perspex.Input.csproj b/Perspex.Input/Perspex.Input.csproj
index d9282a1148..35d8451ee4 100644
--- a/Perspex.Input/Perspex.Input.csproj
+++ b/Perspex.Input/Perspex.Input.csproj
@@ -66,6 +66,7 @@
+
diff --git a/Perspex.Input/VectorEventArgs.cs b/Perspex.Input/VectorEventArgs.cs
new file mode 100644
index 0000000000..be6cf7c1e9
--- /dev/null
+++ b/Perspex.Input/VectorEventArgs.cs
@@ -0,0 +1,16 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Input
+{
+ using System;
+ using Perspex.Interactivity;
+
+ public class VectorEventArgs : RoutedEventArgs
+ {
+ public Vector Vector { get; set; }
+ }
+}
diff --git a/Perspex.Interactivity/Interactive.cs b/Perspex.Interactivity/Interactive.cs
index f9c16d84f5..509ffcedd5 100644
--- a/Perspex.Interactivity/Interactive.cs
+++ b/Perspex.Interactivity/Interactive.cs
@@ -59,6 +59,9 @@ namespace Perspex.Interactivity
{
Contract.Requires(e != null);
+ e.Source = e.Source ?? this;
+ e.OriginalSource = e.OriginalSource ?? this;
+
if (!e.Handled)
{
switch (e.RoutedEvent.RoutingStrategy)
diff --git a/Perspex.Interactivity/RoutedEventArgs.cs b/Perspex.Interactivity/RoutedEventArgs.cs
index 23d97b9818..9e54d1d585 100644
--- a/Perspex.Interactivity/RoutedEventArgs.cs
+++ b/Perspex.Interactivity/RoutedEventArgs.cs
@@ -10,21 +10,6 @@ namespace Perspex.Interactivity
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; }
diff --git a/Perspex.Themes.Default/DefaultTheme.cs b/Perspex.Themes.Default/DefaultTheme.cs
index e4f7f1ee5f..bf33d21b2e 100644
--- a/Perspex.Themes.Default/DefaultTheme.cs
+++ b/Perspex.Themes.Default/DefaultTheme.cs
@@ -15,6 +15,7 @@ namespace Perspex.Themes.Default
this.Add(new ButtonStyle());
this.Add(new CheckBoxStyle());
this.Add(new ContentControlStyle());
+ this.Add(new GridSplitterStyle());
this.Add(new ItemsControlStyle());
this.Add(new TabControlStyle());
this.Add(new TabItemStyle());
diff --git a/Perspex.Themes.Default/GridSplitterStyle.cs b/Perspex.Themes.Default/GridSplitterStyle.cs
new file mode 100644
index 0000000000..99cb0d04aa
--- /dev/null
+++ b/Perspex.Themes.Default/GridSplitterStyle.cs
@@ -0,0 +1,42 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Themes.Default
+{
+ using System.Linq;
+ using Perspex.Controls;
+ using Perspex.Controls.Presenters;
+ using Perspex.Media;
+ using Perspex.Styling;
+
+ public class GridSplitterStyle : Styles
+ {
+ public GridSplitterStyle()
+ {
+ this.AddRange(new[]
+ {
+ new Style(x => x.OfType())
+ {
+ Setters = new[]
+ {
+ new Setter(GridSplitter.TemplateProperty, ControlTemplate.Create(this.Template)),
+ new Setter(GridSplitter.WidthProperty, 4),
+ },
+ },
+ });
+ }
+
+ private Control Template(GridSplitter control)
+ {
+ Border border = new Border
+ {
+ [~Border.BackgroundProperty] = control[~GridSplitter.BackgroundProperty],
+ };
+
+ return border;
+ }
+ }
+}
diff --git a/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/Perspex.Themes.Default/Perspex.Themes.Default.csproj
index 30dd73a5f5..e436a1f65c 100644
--- a/Perspex.Themes.Default/Perspex.Themes.Default.csproj
+++ b/Perspex.Themes.Default/Perspex.Themes.Default.csproj
@@ -66,6 +66,7 @@
+