Browse Source

Merge branch 'master' into leaks

pull/366/head
Steven Kirk 11 years ago
parent
commit
5de2f13ac8
  1. 2
      readme.md
  2. 17
      src/Perspex.Controls/Control.cs
  3. 2
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  4. 9
      src/Perspex.Controls/Shapes/Shape.cs
  5. 2
      src/Perspex.SceneGraph/Media/Pen.cs
  6. 33
      src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs
  7. 1
      src/iOS/Perspex.iOS/Perspex.iOS.csproj
  8. 26
      src/iOS/Perspex.iOS/PerspexView.cs
  9. 147
      src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs
  10. 28
      tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  11. BIN
      tests/Perspex.RenderTests/Shapes/PathTests.cs
  12. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png

2
readme.md

@ -13,7 +13,7 @@ Desktop platforms:
Mobile platforms:
<a href='https://www.youtube.com/watch?t=28&v=c_AB_XSILp0 target='_blank'>![](https://i.ytimg.com/vi/NJ9-hnmUbBM/hqdefault.jpg)<a/>
<a href='https://www.youtube.com/watch?v=NJ9-hnmUbBM' target='_blank'>![](https://i.ytimg.com/vi/NJ9-hnmUbBM/hqdefault.jpg)<a/>
## NuGet

17
src/Perspex.Controls/Control.cs

@ -93,6 +93,15 @@ namespace Perspex.Controls
_nameScope = this as INameScope;
}
/// <summary>
/// Occurs when the <see cref="DataContext"/> property changes.
/// </summary>
/// <remarks>
/// This event will be raised when the <see cref="DataContext"/> property has changed and
/// all subscribers to that change have been notified.
/// </remarks>
public event EventHandler DataContextChanged;
/// <summary>
/// Gets or sets the control's classes.
/// </summary>
@ -394,8 +403,9 @@ namespace Perspex.Controls
/// Called when the <see cref="DataContext"/> is changed and all subscribers to that change
/// have been notified.
/// </summary>
protected virtual void OnDataContextFinishedChanging()
protected virtual void OnDataContextChanged()
{
DataContextChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
@ -419,6 +429,11 @@ namespace Perspex.Controls
if (control != null)
{
control.IsDataContextChanging = notifying;
if (!notifying)
{
control.OnDataContextChanged();
}
}
}
}

2
src/Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -287,7 +287,7 @@ namespace Perspex.Controls.Primitives
}
/// <inheritdoc/>
protected override void OnDataContextFinishedChanging()
protected override void OnDataContextChanged()
{
if (_clearSelectedItemsAfterDataContextChanged == SelectedItems)
{

9
src/Perspex.Controls/Shapes/Shape.cs

@ -90,13 +90,20 @@ namespace Perspex.Controls.Shapes
set { SetValue(StrokeThicknessProperty, value); }
}
public PenLineCap StrokeDashCap { get; set; } = PenLineCap.Flat;
public PenLineCap StrokeStartLineCap { get; set; } = PenLineCap.Flat;
public PenLineCap StrokeEndLineCap { get; set; } = PenLineCap.Flat;
public override void Render(DrawingContext context)
{
var geometry = RenderedGeometry;
if (geometry != null)
{
var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray));
var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray),
StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap);
context.DrawGeometry(Fill, pen, geometry);
}
}

2
src/Perspex.SceneGraph/Media/Pen.cs

@ -31,12 +31,12 @@ namespace Perspex.Media
{
Brush = brush;
Thickness = thickness;
DashCap = dashCap;
StartLineCap = startLineCap;
EndLineCap = endLineCap;
LineJoin = lineJoin;
MiterLimit = miterLimit;
DashStyle = dashStyle;
DashCap = dashCap;
}
/// <summary>

33
src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs

@ -71,26 +71,23 @@ namespace Perspex.Direct2D1
/// <returns>The Direct2D brush.</returns>
public static StrokeStyle ToDirect2DStrokeStyle(this Perspex.Media.Pen pen, SharpDX.Direct2D1.RenderTarget target)
{
if (pen.DashStyle != null)
var properties = new StrokeStyleProperties
{
if (pen.DashStyle.Dashes != null && pen.DashStyle.Dashes.Count > 0)
{
var properties = new StrokeStyleProperties
{
DashStyle = DashStyle.Custom,
DashOffset = (float)pen.DashStyle.Offset,
MiterLimit = (float)pen.MiterLimit,
LineJoin = pen.LineJoin.ToDirect2D(),
StartCap = pen.StartLineCap.ToDirect2D(),
EndCap = pen.EndLineCap.ToDirect2D(),
DashCap = pen.DashCap.ToDirect2D()
};
return new StrokeStyle(target.Factory, properties, pen.DashStyle?.Dashes.Select(x => (float)x).ToArray());
}
DashStyle = DashStyle.Solid,
MiterLimit = (float)pen.MiterLimit,
LineJoin = pen.LineJoin.ToDirect2D(),
StartCap = pen.StartLineCap.ToDirect2D(),
EndCap = pen.EndLineCap.ToDirect2D(),
DashCap = pen.DashCap.ToDirect2D()
};
var dashes = new float[0];
if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0)
{
properties.DashStyle = DashStyle.Custom;
properties.DashOffset = (float)pen.DashStyle.Offset;
dashes = pen.DashStyle?.Dashes.Select(x => (float)x).ToArray();
}
return null;
return new StrokeStyle(target.Factory, properties, dashes);
}
/// <summary>

1
src/iOS/Perspex.iOS/Perspex.iOS.csproj

@ -42,6 +42,7 @@
<Compile Include="PlatformSettings.cs" />
<Compile Include="PlatformThreadingInterface.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Specific\KeyboardEventsHelper.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Perspex.Animation\Perspex.Animation.csproj">

26
src/iOS/Perspex.iOS/PerspexView.cs

@ -14,26 +14,41 @@ using Perspex.Media;
using Perspex.Platform;
using Perspex.Skia.iOS;
using UIKit;
using Perspex.iOS.Specific;
using ObjCRuntime;
namespace Perspex.iOS
{
[Adopts("UIKeyInput")]
class PerspexView : SkiaView, IWindowImpl
{
private readonly UIWindow _window;
private readonly UIViewController _controller;
private IInputRoot _inputRoot;
private readonly KeyboardEventsHelper<PerspexView> _keyboardHelper;
public PerspexView(UIWindow window, UIViewController controller) : base(onFrame => PlatformThreadingInterface.Instance.Render = onFrame)
{
if (controller == null) throw new ArgumentNullException(nameof(controller));
_window = window;
_controller = controller;
_keyboardHelper = new KeyboardEventsHelper<PerspexView>(this);
AutoresizingMask = UIViewAutoresizing.All;
AutoFit();
UIApplication.Notifications.ObserveDidChangeStatusBarOrientation(delegate { AutoFit(); });
UIApplication.Notifications.ObserveDidChangeStatusBarFrame(delegate { AutoFit(); });
}
[Export("hasText")]
bool HasText => _keyboardHelper.HasText();
[Export("insertText:")]
void InsertText(string text) => _keyboardHelper.InsertText(text);
[Export("deleteBackward")]
void DeleteBackward() => _keyboardHelper.DeleteBackward();
public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder();
void AutoFit()
{
@ -89,6 +104,7 @@ namespace Perspex.iOS
public void Show()
{
_keyboardHelper.ActivateAutoShowKeybord();
}
public Size MaxClientSize => Bounds.Size.ToPerspex();
@ -152,8 +168,14 @@ namespace Perspex.iOS
RawMouseEventType.Move, location, InputModifiers.LeftMouseButton));
else
{
Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp,
_inputRoot, location, location - _touchLastPoint, InputModifiers.LeftMouseButton));
double x = location.X - _touchLastPoint.X;
double y = location.Y - _touchLastPoint.Y;
double correction = 0.02;
var scale = PerspexLocator.Current.GetService<IPlatformSettings>().RenderScalingFactor;
scale = 1;
Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint)touch.Timestamp,
_inputRoot, location, new Vector(x * correction / scale, y * correction / scale), InputModifiers.LeftMouseButton));
}
_touchLastPoint = location;
}

147
src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs

@ -0,0 +1,147 @@
using ObjCRuntime;
using Perspex.Controls;
using Perspex.Input;
using Perspex.Input.Raw;
using Perspex.Platform;
using System;
using System.ComponentModel;
using System.Linq;
using UIKit;
namespace Perspex.iOS.Specific
{
/// <summary>
/// In order to have properly handle of keyboard event in iOS View should already made some things in the View:
/// 1. Adopt the UIKeyInput protocol - add [Adopts("UIKeyInput")] to your view class
/// 2. Implement all the methods required by UIKeyInput:
/// 2.1 Implement HasText
/// example:
/// [Export("hasText")]
/// bool HasText => _keyboardHelper.HasText()
/// 2.2 Implement InsertText
/// example:
/// [Export("insertText:")]
/// void InsertText(string text) => _keyboardHelper.InsertText(text);
/// 2.3 Implement InsertText
/// example:
/// [Export("deleteBackward")]
/// void DeleteBackward() => _keyboardHelper.DeleteBackward();
/// 3.Let iOS know that this can become a first responder:
/// public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder();
/// or
/// public override bool CanBecomeFirstResponder { get { return true; } }
///
/// 4. To show keyboard:
/// view.BecomeFirstResponder();
/// 5. To hide keyboard
/// view.ResignFirstResponder();
/// </summary>
/// <typeparam name="TView">View that needs keyboard events and show/hide keyboard</typeparam>
internal class KeyboardEventsHelper<TView> where TView : UIView, IWindowImpl
{
private TView _view;
private IInputElement _lastFocusedElement;
public KeyboardEventsHelper(TView view)
{
_view = view;
var uiKeyInputAttribute = view.GetType().GetCustomAttributes(typeof(AdoptsAttribute), true).OfType<AdoptsAttribute>().Where(a => a.ProtocolType == "UIKeyInput").FirstOrDefault();
if (uiKeyInputAttribute == null) throw new NotSupportedException($"View class {typeof(TView).Name} should have class attribute - [Adopts(\"UIKeyInput\")] in order to access keyboard events!");
HandleEvents = true;
}
/// <summary>
/// HandleEvents in order to suspend keyboard notifications or resume it
/// </summary>
public bool HandleEvents { get; set; }
public bool HasText() => false;
public bool CanBecomeFirstResponder() => true;
public void DeleteBackward()
{
HandleKey(Key.Back, RawKeyEventType.KeyDown);
HandleKey(Key.Back, RawKeyEventType.KeyUp);
}
public void InsertText(string text)
{
var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, text);
_view.Input(rawTextEvent);
}
private void HandleKey(Key key, RawKeyEventType type)
{
var rawKeyEvent = new RawKeyEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, type, key, InputModifiers.None);
_view.Input(rawKeyEvent);
}
//currently not found a way to get InputModifiers state
//private static InputModifiers GetModifierKeys(object e)
//{
// var im = InputModifiers.None;
// //if (IsCtrlPressed) rv |= InputModifiers.Control;
// //if (IsShiftPressed) rv |= InputModifiers.Shift;
// return im;
//}
private bool NeedsKeyboard(IInputElement element)
{
//may be some other elements
return element is TextBox;
}
private void TryShowHideKeyboard(IInputElement element, bool value)
{
if (value)
{
_view.BecomeFirstResponder();
}
else
{
_view.ResignFirstResponder();
}
}
public void UpdateKeyboardState(IInputElement element)
{
var focusedElement = element;
bool oldValue = NeedsKeyboard(_lastFocusedElement);
bool newValue = NeedsKeyboard(focusedElement);
if (newValue != oldValue || newValue)
{
TryShowHideKeyboard(focusedElement, newValue);
}
_lastFocusedElement = element;
}
public void ActivateAutoShowKeybord()
{
var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged);
//just in case we've called more than once the method
kbDevice.PropertyChanged -= KeyboardDevice_PropertyChanged;
kbDevice.PropertyChanged += KeyboardDevice_PropertyChanged;
}
private void KeyboardDevice_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(KeyboardDevice.FocusedElement))
{
UpdateKeyboardState(KeyboardDevice.Instance.FocusedElement);
}
}
public void Dispose()
{
HandleEvents = false;
}
}
}

28
tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -427,6 +427,34 @@ namespace Perspex.Controls.UnitTests.Primitives
// Clear DataContext and ensure that SelectedItems is still set in the VM.
target.DataContext = null;
Assert.Equal(new[] { "bar" }, vm.SelectedItems);
// Ensure target's SelectedItems is now clear.
Assert.Empty(target.SelectedItems);
}
[Fact]
public void Unbound_SelectedItems_Should_Be_Cleared_When_DataContext_Cleared()
{
var data = new
{
Items = new[] { "foo", "bar", "baz" },
};
var target = new TestSelector
{
DataContext = data,
Template = Template(),
};
var itemsBinding = new Binding { Path = "Items" };
itemsBinding.Bind(target, TestSelector.ItemsProperty);
Assert.Same(data.Items, target.Items);
target.SelectedItems.Add("bar");
target.DataContext = null;
Assert.Empty(target.SelectedItems);
}
private FuncControlTemplate Template()

BIN
tests/Perspex.RenderTests/Shapes/PathTests.cs

Binary file not shown.

BIN
tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Loading…
Cancel
Save