68 changed files with 1018 additions and 252 deletions
@ -0,0 +1,33 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Markup.Xaml.Binding; |
|||
using OmniXaml; |
|||
using OmniXaml.Typing; |
|||
using System.Reflection; |
|||
|
|||
namespace Perspex.Markup.Xaml.Context |
|||
{ |
|||
public class PerspexAttachableXamlMember : AttachableXamlMember |
|||
{ |
|||
public PerspexAttachableXamlMember(string name, |
|||
XamlType owner, |
|||
MethodInfo getter, |
|||
MethodInfo setter, |
|||
IXamlTypeRepository xamlTypeRepository, |
|||
ITypeFeatureProvider featureProvider) |
|||
: base(name, getter, setter, xamlTypeRepository, featureProvider) |
|||
{ |
|||
} |
|||
|
|||
protected override IXamlMemberValuePlugin LookupXamlMemberValueConnector() |
|||
{ |
|||
return new PerspexXamlMemberValuePlugin(this); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return "Perspex Attachable XAML Member " + base.ToString(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Perspex.Platform; |
|||
|
|||
namespace Perspex |
|||
{ |
|||
public abstract class PerspexDisposable : IDisposable |
|||
{ |
|||
#if DEBUG_DISPOSE
|
|||
public string DisposedAt { get; private set; } |
|||
#endif
|
|||
|
|||
|
|||
public bool IsDisposed { get; private set; } |
|||
|
|||
public void Dispose() |
|||
{ |
|||
IsDisposed = true; |
|||
#if DEBUG_DISPOSE
|
|||
DisposedAt = PerspexLocator.Current.GetService<IPclPlatformWrapper>().GetStackTrace(); |
|||
#endif
|
|||
DoDispose(); |
|||
} |
|||
|
|||
protected void CheckDisposed() |
|||
{ |
|||
if (IsDisposed) |
|||
throw new ObjectDisposedException(GetType().FullName |
|||
#if DEBUG_DISPOSE
|
|||
, "Disposed at: \n" + DisposedAt |
|||
#endif
|
|||
|
|||
); |
|||
} |
|||
|
|||
protected abstract void DoDispose(); |
|||
} |
|||
} |
|||
@ -1,11 +1,23 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Controls.Primitives; |
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the placement for a <see cref="Popup"/> control.
|
|||
/// </summary>
|
|||
public enum PlacementMode |
|||
{ |
|||
/// <summary>
|
|||
/// The popup is placed at the bottom left of its target.
|
|||
/// </summary>
|
|||
Bottom, |
|||
|
|||
/// <summary>
|
|||
/// The popup is placed at the top right of its target.
|
|||
/// </summary>
|
|||
Right, |
|||
} |
|||
} |
|||
|
|||
@ -1,18 +1,27 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Perspex.Controls; |
|||
using Perspex.Input.Raw; |
|||
|
|||
namespace Perspex.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a platform-specific popup window implementation.
|
|||
/// </summary>
|
|||
public interface IPopupImpl : ITopLevelImpl |
|||
{ |
|||
/// <summary>
|
|||
/// Sets the position of the popup.
|
|||
/// </summary>
|
|||
/// <param name="p">The position, in screen coordinates.</param>
|
|||
void SetPosition(Point p); |
|||
|
|||
/// <summary>
|
|||
/// Shows the popup.
|
|||
/// </summary>
|
|||
void Show(); |
|||
|
|||
/// <summary>
|
|||
/// Hides the popup.
|
|||
/// </summary>
|
|||
void Hide(); |
|||
} |
|||
} |
|||
|
|||
@ -1,14 +1,22 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Perspex.Platform; |
|||
|
|||
namespace Perspex.Controls.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a platform-specific system dialog implementation.
|
|||
/// </summary>
|
|||
public interface ISystemDialogImpl |
|||
{ |
|||
/// <summary>
|
|||
/// Shows a file dialog.
|
|||
/// </summary>
|
|||
/// <param name="dialog">The details of the file dialog to show.</param>
|
|||
/// <param name="parent">The parent window.</param>
|
|||
/// <returns>A task returning the selected filenames.</returns>
|
|||
Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,206 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Perspex.Media.Imaging; |
|||
|
|||
namespace Perspex.Media |
|||
{ |
|||
public sealed class DrawingContext : IDisposable |
|||
{ |
|||
private readonly IDrawingContextImpl _impl; |
|||
private int _currentLevel; |
|||
|
|||
private Stack<TransformContainer> _transformContainers = new Stack<TransformContainer>(); |
|||
|
|||
struct TransformContainer |
|||
{ |
|||
public Matrix LocalTransform; |
|||
public Matrix ContainerTransform; |
|||
|
|||
public TransformContainer(Matrix localTransform, Matrix containerTransform) |
|||
{ |
|||
LocalTransform = localTransform; |
|||
ContainerTransform = containerTransform; |
|||
} |
|||
} |
|||
|
|||
public DrawingContext(IDrawingContextImpl impl) |
|||
{ |
|||
_impl = impl; |
|||
} |
|||
|
|||
|
|||
private Matrix _currentTransform = Matrix.Identity; |
|||
|
|||
/// <summary>
|
|||
/// Gets the current transform of the drawing context.
|
|||
/// </summary>
|
|||
public Matrix CurrentTransform |
|||
{ |
|||
get { return _currentTransform; } |
|||
private set |
|||
{ |
|||
_currentTransform = value; |
|||
_impl.Transform = _currentTransform*_currentContainerTransform; |
|||
} |
|||
} |
|||
|
|||
private Matrix _currentContainerTransform = Matrix.Identity; |
|||
|
|||
/// <summary>
|
|||
/// Draws a bitmap image.
|
|||
/// </summary>
|
|||
/// <param name="source">The bitmap image.</param>
|
|||
/// <param name="opacity">The opacity to draw with.</param>
|
|||
/// <param name="sourceRect">The rect in the image to draw.</param>
|
|||
/// <param name="destRect">The rect in the output to draw to.</param>
|
|||
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect) |
|||
=> _impl.DrawImage(source, opacity, sourceRect, destRect); |
|||
|
|||
/// <summary>
|
|||
/// Draws a line.
|
|||
/// </summary>
|
|||
/// <param name="pen">The stroke pen.</param>
|
|||
/// <param name="p1">The first point of the line.</param>
|
|||
/// <param name="p2">The second point of the line.</param>
|
|||
public void DrawLine(Pen pen, Point p1, Point p2) => _impl.DrawLine(pen, p1, p2); |
|||
|
|||
/// <summary>
|
|||
/// Draws a geometry.
|
|||
/// </summary>
|
|||
/// <param name="brush">The fill brush.</param>
|
|||
/// <param name="pen">The stroke pen.</param>
|
|||
/// <param name="geometry">The geometry.</param>
|
|||
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) => _impl.DrawGeometry(brush, pen, geometry); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of a rectangle.
|
|||
/// </summary>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="rect">The rectangle bounds.</param>
|
|||
/// <param name="cornerRadius">The corner radius.</param>
|
|||
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0.0f) |
|||
=> _impl.DrawRectangle(pen, rect, cornerRadius); |
|||
|
|||
/// <summary>
|
|||
/// Draws text.
|
|||
/// </summary>
|
|||
/// <param name="foreground">The foreground brush.</param>
|
|||
/// <param name="origin">The upper-left corner of the text.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
public void DrawText(Brush foreground, Point origin, FormattedText text) |
|||
=> _impl.DrawText(foreground, origin, text); |
|||
|
|||
/// <summary>
|
|||
/// Draws a filled rectangle.
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="rect">The rectangle bounds.</param>
|
|||
/// <param name="cornerRadius">The corner radius.</param>
|
|||
public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0.0f) |
|||
=> _impl.FillRectangle(brush, rect, cornerRadius); |
|||
|
|||
public struct PushedState : IDisposable |
|||
{ |
|||
private readonly int _level; |
|||
private readonly DrawingContext _context; |
|||
private readonly Matrix _matrix; |
|||
private readonly PushedStateType _type; |
|||
|
|||
public enum PushedStateType |
|||
{ |
|||
None, |
|||
Matrix, |
|||
Opacity, |
|||
Clip, |
|||
MatrixContainer |
|||
} |
|||
|
|||
public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) |
|||
{ |
|||
_level = context._currentLevel += 1; |
|||
_context = context; |
|||
_type = type; |
|||
_matrix = matrix; |
|||
|
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if(_type == PushedStateType.None) |
|||
return; |
|||
if (_context._currentLevel != _level) |
|||
throw new InvalidOperationException("Wrong Push/Pop state order"); |
|||
_context._currentLevel--; |
|||
if (_type == PushedStateType.Matrix) |
|||
_context.CurrentTransform = _matrix; |
|||
else if(_type == PushedStateType.Clip) |
|||
_context._impl.PopClip(); |
|||
else if(_type == PushedStateType.Opacity) |
|||
_context._impl.PopOpacity(); |
|||
else if (_type == PushedStateType.MatrixContainer) |
|||
{ |
|||
var cont = _context._transformContainers.Pop(); |
|||
_context._currentContainerTransform = cont.ContainerTransform; |
|||
_context.CurrentTransform = cont.LocalTransform; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Pushes a clip rectange.
|
|||
/// </summary>
|
|||
/// <param name="clip">The clip rectangle.</param>
|
|||
/// <returns>A disposable used to undo the clip rectangle.</returns>
|
|||
public PushedState PushClip(Rect clip) |
|||
{ |
|||
_impl.PushClip(clip); |
|||
return new PushedState(this, PushedState.PushedStateType.Clip); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Pushes an opacity value.
|
|||
/// </summary>
|
|||
/// <param name="opacity">The opacity.</param>
|
|||
/// <returns>A disposable used to undo the opacity.</returns>
|
|||
public PushedState PushOpacity(double opacity) |
|||
//TODO: Elimintate platform-specific push opacity call
|
|||
{ |
|||
_impl.PushOpacity(opacity); |
|||
return new PushedState(this, PushedState.PushedStateType.Opacity); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Pushes a matrix transformation.
|
|||
/// </summary>
|
|||
/// <param name="matrix">The matrix</param>
|
|||
/// <returns>A disposable used to undo the transformation.</returns>
|
|||
public PushedState PushPostTransform(Matrix matrix) => PushSetTransform(CurrentTransform*matrix); |
|||
|
|||
public PushedState PushPreTransform(Matrix matrix) => PushSetTransform(matrix*CurrentTransform); |
|||
|
|||
|
|||
PushedState PushSetTransform(Matrix matrix) |
|||
{ |
|||
var oldMatrix = CurrentTransform; |
|||
CurrentTransform = matrix; |
|||
|
|||
return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix); |
|||
} |
|||
|
|||
|
|||
public PushedState PushTransformContainer() |
|||
{ |
|||
_transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform)); |
|||
_currentContainerTransform = CurrentTransform*_currentContainerTransform; |
|||
_currentTransform = Matrix.Identity; |
|||
return new PushedState(this, PushedState.PushedStateType.MatrixContainer); |
|||
} |
|||
|
|||
public void Dispose() => _impl.Dispose(); |
|||
} |
|||
} |
|||
@ -1,76 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Reactive.Disposables; |
|||
using Perspex.Media.Imaging; |
|||
|
|||
namespace Perspex.Media |
|||
{ |
|||
public class ValidatingDrawingContext : IDrawingContext |
|||
{ |
|||
private readonly IDrawingContext _base; |
|||
|
|||
public ValidatingDrawingContext(IDrawingContext @base) |
|||
{ |
|||
_base = @base; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_base.Dispose(); |
|||
} |
|||
|
|||
public Matrix CurrentTransform => _base.CurrentTransform; |
|||
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect) |
|||
{ |
|||
_base.DrawImage(source, opacity, sourceRect, destRect); |
|||
} |
|||
|
|||
public void DrawLine(Pen pen, Point p1, Point p2) |
|||
{ |
|||
_base.DrawLine(pen, p1, p2); |
|||
} |
|||
|
|||
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) |
|||
{ |
|||
_base.DrawGeometry(brush, pen, geometry); |
|||
} |
|||
|
|||
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0) |
|||
{ |
|||
_base.DrawRectangle(pen, rect, cornerRadius); |
|||
} |
|||
|
|||
public void DrawText(Brush foreground, Point origin, FormattedText text) |
|||
{ |
|||
_base.DrawText(foreground, origin, text); |
|||
} |
|||
|
|||
public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0) |
|||
{ |
|||
_base.FillRectangle(brush, rect, cornerRadius); |
|||
} |
|||
|
|||
|
|||
Stack<IDisposable> _stateStack = new Stack<IDisposable>(); |
|||
|
|||
IDisposable Transform(IDisposable disposable) |
|||
{ |
|||
_stateStack.Push(disposable); |
|||
return Disposable.Create(() => |
|||
{ |
|||
var current = _stateStack.Peek(); |
|||
if (current != disposable) |
|||
throw new InvalidOperationException("Invalid push/pop order"); |
|||
current.Dispose(); |
|||
_stateStack.Pop(); |
|||
}); |
|||
} |
|||
|
|||
public IDisposable PushClip(Rect clip) => Transform(_base.PushClip(clip)); |
|||
|
|||
public IDisposable PushOpacity(double opacity) => Transform(_base.PushOpacity(opacity)); |
|||
|
|||
public IDisposable PushTransform(Matrix matrix) => Transform(_base.PushTransform(matrix)); |
|||
} |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,101 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Perspex.Controls; |
|||
using Perspex.Controls.Presenters; |
|||
using Perspex.Controls.Templates; |
|||
using Perspex.LogicalTree; |
|||
using Perspex.Styling; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Controls.UnitTests |
|||
{ |
|||
public class TreeViewTests |
|||
{ |
|||
[Fact] |
|||
public void LogicalChildren_Should_Be_Set() |
|||
{ |
|||
var target = new TreeView |
|||
{ |
|||
Template = new ControlTemplate(CreateTreeViewTemplate), |
|||
Items = new[] { "Foo", "Bar", "Baz " }, |
|||
}; |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.Equal(3, target.GetLogicalChildren().Count()); |
|||
|
|||
foreach (var child in target.GetLogicalChildren()) |
|||
{ |
|||
Assert.IsType<TreeViewItem>(child); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DataContexts_Should_Be_Correctly_Set() |
|||
{ |
|||
var items = new object[] |
|||
{ |
|||
"Foo", |
|||
new Item("Bar"), |
|||
new TextBlock { Text = "Baz" }, |
|||
new TreeViewItem { Header = "Qux" }, |
|||
}; |
|||
|
|||
var target = new TreeView |
|||
{ |
|||
Template = new ControlTemplate(CreateTreeViewTemplate), |
|||
DataContext = "Base", |
|||
DataTemplates = new DataTemplates |
|||
{ |
|||
new FuncDataTemplate<Item>(x => new Button { Content = x }) |
|||
}, |
|||
Items = items, |
|||
}; |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
var dataContexts = target.Presenter.Panel.Children |
|||
.Cast<Control>() |
|||
.Select(x => x.DataContext) |
|||
.ToList(); |
|||
|
|||
Assert.Equal( |
|||
new object[] { items[0], items[1], "Base", "Base" }, |
|||
dataContexts); |
|||
} |
|||
|
|||
private Control CreateTreeViewTemplate(ITemplatedControl parent) |
|||
{ |
|||
return new ScrollViewer |
|||
{ |
|||
Template = new ControlTemplate(CreateScrollViewerTemplate), |
|||
Content = new ItemsPresenter |
|||
{ |
|||
Name = "itemsPresenter", |
|||
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty), |
|||
} |
|||
}; |
|||
} |
|||
|
|||
private Control CreateScrollViewerTemplate(ITemplatedControl parent) |
|||
{ |
|||
return new ScrollContentPresenter |
|||
{ |
|||
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty), |
|||
}; |
|||
} |
|||
|
|||
private class Item |
|||
{ |
|||
public Item(string value) |
|||
{ |
|||
Value = value; |
|||
} |
|||
|
|||
public string Value { get; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using Perspex.Markup.Binding; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Markup.UnitTests.Binding |
|||
{ |
|||
public class ExpressionObserverTests_SetValue |
|||
{ |
|||
[Fact] |
|||
public void Should_Set_Simple_Property_Value() |
|||
{ |
|||
var data = new { Foo = "foo" }; |
|||
var target = new ExpressionObserver(data, "Foo"); |
|||
|
|||
target.SetValue("bar"); |
|||
|
|||
Assert.Equal("foo", data.Foo); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Set_Value_On_Simple_Property_Chain() |
|||
{ |
|||
var data = new Class1 { Foo = new Class2 { Bar = "bar" } }; |
|||
var target = new ExpressionObserver(data, "Foo.Bar"); |
|||
|
|||
target.SetValue("foo"); |
|||
|
|||
Assert.Equal("foo", data.Foo.Bar); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Try_To_Set_Value_On_Broken_Chain() |
|||
{ |
|||
var data = new Class1 { Foo = new Class2 { Bar = "bar" } }; |
|||
var target = new ExpressionObserver(data, "Foo.Bar"); |
|||
|
|||
// Ensure the ExpressionObserver's subscriptions are kept active.
|
|||
target.OfType<string>().Subscribe(x => { }); |
|||
|
|||
data.Foo = null; |
|||
|
|||
Assert.False(target.SetValue("foo")); |
|||
} |
|||
|
|||
private class Class1 : NotifyingBase |
|||
{ |
|||
private Class2 _foo; |
|||
|
|||
public Class2 Foo |
|||
{ |
|||
get { return _foo; } |
|||
set |
|||
{ |
|||
_foo = value; |
|||
RaisePropertyChanged(nameof(Foo)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private class Class2 : NotifyingBase |
|||
{ |
|||
private string _bar; |
|||
|
|||
public string Bar |
|||
{ |
|||
get { return _bar; } |
|||
set |
|||
{ |
|||
_bar = value; |
|||
RaisePropertyChanged(nameof(Bar)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Perspex.Markup.Xaml.Templates; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Markup.Xaml.UnitTests.Templates |
|||
{ |
|||
public class DataTemplateTests |
|||
{ |
|||
[Fact] |
|||
public void DataTemplate_Should_Match_Data_Of_Type() |
|||
{ |
|||
var target = new DataTemplate { DataType = typeof(Class1) }; |
|||
var data = new Class1(); |
|||
|
|||
Assert.True(target.Match(data)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DataTemplate_Should_Match_Data_Of_Derived_Type() |
|||
{ |
|||
var target = new DataTemplate { DataType = typeof(Class1) }; |
|||
var data = new Class2(); |
|||
|
|||
Assert.True(target.Match(data)); |
|||
} |
|||
|
|||
private class Class1 { } |
|||
private class Class2 : Class1 { } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue