Browse Source

Merge branch 'master' into leaks

Steven Kirk 10 years ago
parent
commit
5de2f13ac8

+ 1 - 1
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
 

+ 16 - 1
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();
+                }
             }
         }
     }

+ 1 - 1
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)
             {

+ 8 - 1
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);
             }
         }

+ 1 - 1
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>

+ 15 - 18
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 - 0
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">

+ 24 - 2
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 - 0
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 - 0
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


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