Browse Source

Merge branch 'master' into wpfwrappanelimport

pull/2596/head
Wiesław Šoltés 7 years ago
committed by GitHub
parent
commit
f9c48b0257
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      native/Avalonia.Native/inc/avalonia-native.h
  2. 10
      native/Avalonia.Native/src/OSX/cursor.h
  3. 5
      native/Avalonia.Native/src/OSX/cursor.mm
  4. 10
      native/Avalonia.Native/src/OSX/window.mm
  5. 3
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  6. 1
      src/Avalonia.Input/Cursors.cs
  7. 5
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  8. 55
      src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs
  9. 143
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  10. 75
      src/Avalonia.ReactiveUI/TransitioningContentControl.cs
  11. 80
      src/Avalonia.ReactiveUI/ViewModelViewHost.cs
  12. 10
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  13. 10
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  14. 6
      src/Avalonia.Themes.Default/NotificationCard.xaml
  15. 8
      src/Avalonia.Visuals/Animation/CrossFade.cs
  16. 24
      src/Avalonia.X11/X11CursorFactory.cs
  17. 3
      src/Avalonia.X11/XLib.cs
  18. 3
      src/Gtk/Avalonia.Gtk3/CursorFactory.cs
  19. 1
      src/Gtk/Avalonia.Gtk3/GdkCursor.cs
  20. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  21. 1
      src/Windows/Avalonia.Win32/CursorFactory.cs
  22. 35
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  23. 9
      tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs
  24. 116
      tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs
  25. 5
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  26. 34
      tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
  27. 37
      tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs
  28. 8
      tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
  29. 63
      tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs
  30. 74
      tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs

1
native/Avalonia.Native/inc/avalonia-native.h

@ -144,6 +144,7 @@ enum AvnStandardCursorType
CursorDragMove,
CursorDragCopy,
CursorDragLink,
CursorNone
};
enum AvnWindowEdge

10
native/Avalonia.Native/src/OSX/cursor.h

@ -11,18 +11,24 @@ class Cursor : public ComSingleObject<IAvnCursor, &IID_IAvnCursor>
{
private:
NSCursor * _native;
bool _isHidden;
public:
FORWARD_IUNKNOWN()
Cursor(NSCursor * cursor)
Cursor(NSCursor * cursor, bool isHidden = false)
{
_native = cursor;
_isHidden = isHidden;
}
NSCursor* GetNative()
{
return _native;
}
bool IsHidden ()
{
return _isHidden;
}
};
extern std::map<AvnStandardCursorType, Cursor*> s_cursorMap;

5
native/Avalonia.Native/src/OSX/cursor.mm

@ -21,6 +21,7 @@ class CursorFactory : public ComSingleObject<IAvnCursorFactory, &IID_IAvnCursorF
Cursor* resizeRightCursor = new Cursor([NSCursor resizeRightCursor]);
Cursor* resizeWestEastCursor = new Cursor([NSCursor resizeLeftRightCursor]);
Cursor* operationNotAllowedCursor = new Cursor([NSCursor operationNotAllowedCursor]);
Cursor* noCursor = new Cursor([NSCursor arrowCursor], true);
std::map<AvnStandardCursorType, Cursor*> s_cursorMap =
{
@ -46,11 +47,13 @@ class CursorFactory : public ComSingleObject<IAvnCursorFactory, &IID_IAvnCursorF
{ CursorIbeam, IBeamCursor },
{ CursorLeftSide, resizeLeftCursor },
{ CursorRightSide, resizeRightCursor },
{ CursorNo, operationNotAllowedCursor }
{ CursorNo, operationNotAllowedCursor },
{ CursorNone, noCursor }
};
public:
FORWARD_IUNKNOWN()
virtual HRESULT GetCursor (AvnStandardCursorType cursorType, IAvnCursor** retOut) override
{
*retOut = s_cursorMap[cursorType];

10
native/Avalonia.Native/src/OSX/window.mm

@ -353,6 +353,16 @@ public:
Cursor* avnCursor = dynamic_cast<Cursor*>(cursor);
this->cursor = avnCursor->GetNative();
UpdateCursor();
if(avnCursor->IsHidden())
{
[NSCursor hide];
}
else
{
[NSCursor unhide];
}
return S_OK;
}
}

3
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -54,7 +54,8 @@ namespace Avalonia.Controls.Primitives
nameof(SelectedIndex),
o => o.SelectedIndex,
(o, v) => o.SelectedIndex = v,
unsetValue: -1);
unsetValue: -1,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="SelectedItem"/> property.

1
src/Avalonia.Input/Cursors.cs

@ -38,6 +38,7 @@ namespace Avalonia.Input
DragMove,
DragCopy,
DragLink,
None,
// Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/
// We might enable them later, preferably, by loading pixmax direclty from theme with fallback image

5
src/Avalonia.ReactiveUI/AppBuilderExtensions.cs

@ -21,9 +21,8 @@ namespace Avalonia.ReactiveUI
return builder.AfterSetup(_ =>
{
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
Locator.CurrentMutable.Register(
() => new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
});
}
}

55
src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs

@ -0,0 +1,55 @@
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Templates;
using ReactiveUI;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// AutoDataTemplateBindingHook is a binding hook that checks ItemsControls
/// that don't have DataTemplates, and assigns a default DataTemplate that
/// loads the View associated with each ViewModel.
/// </summary>
public class AutoDataTemplateBindingHook : IPropertyBindingHook
{
private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate<object>(x =>
{
var control = new ViewModelViewHost();
var context = control.GetObservable(Control.DataContextProperty);
control.Bind(ViewModelViewHost.ViewModelProperty, context);
control.HorizontalContentAlignment = HorizontalAlignment.Stretch;
control.VerticalContentAlignment = VerticalAlignment.Stretch;
return control;
},
true);
/// <inheritdoc/>
public bool ExecuteHook(
object source, object target,
Func<IObservedChange<object, object>[]> getCurrentViewModelProperties,
Func<IObservedChange<object, object>[]> getCurrentViewProperties,
BindingDirection direction)
{
var viewProperties = getCurrentViewProperties();
var lastViewProperty = viewProperties.LastOrDefault();
var itemsControl = lastViewProperty?.Sender as ItemsControl;
if (itemsControl == null)
return true;
var propertyName = viewProperties.Last().GetPropertyName();
if (propertyName != "Items" &&
propertyName != "ItemsSource")
return true;
if (itemsControl.ItemTemplate != null)
return true;
itemsControl.ItemTemplate = DefaultItemTemplate;
return true;
}
}
}

143
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -53,33 +53,13 @@ namespace Avalonia.ReactiveUI
/// ReactiveUI routing documentation website</see> for more info.
/// </para>
/// </remarks>
public class RoutedViewHost : UserControl, IActivatable, IEnableLogger
public class RoutedViewHost : TransitioningContentControl, IActivatable, IEnableLogger
{
/// <summary>
/// The router dependency property.
/// <see cref="AvaloniaProperty"/> for the <see cref="Router"/> property.
/// </summary>
public static readonly AvaloniaProperty<RoutingState> RouterProperty =
AvaloniaProperty.Register<RoutedViewHost, RoutingState>(nameof(Router));
/// <summary>
/// The default content property.
/// </summary>
public static readonly AvaloniaProperty<object> DefaultContentProperty =
AvaloniaProperty.Register<RoutedViewHost, object>(nameof(DefaultContent));
/// <summary>
/// Fade in animation property.
/// </summary>
public static readonly AvaloniaProperty<IAnimation> FadeInAnimationProperty =
AvaloniaProperty.Register<RoutedViewHost, IAnimation>(nameof(DefaultContent),
CreateOpacityAnimation(0d, 1d, TimeSpan.FromSeconds(0.25)));
/// <summary>
/// Fade out animation property.
/// </summary>
public static readonly AvaloniaProperty<IAnimation> FadeOutAnimationProperty =
AvaloniaProperty.Register<RoutedViewHost, IAnimation>(nameof(DefaultContent),
CreateOpacityAnimation(1d, 0d, TimeSpan.FromSeconds(0.25)));
/// <summary>
/// Initializes a new instance of the <see cref="RoutedViewHost"/> class.
@ -104,42 +84,6 @@ namespace Avalonia.ReactiveUI
set => SetValue(RouterProperty, value);
}
/// <summary>
/// Gets or sets the content displayed whenever there is no page currently routed.
/// </summary>
public object DefaultContent
{
get => GetValue(DefaultContentProperty);
set => SetValue(DefaultContentProperty, value);
}
/// <summary>
/// Gets or sets the animation played when page appears.
/// </summary>
public IAnimation FadeInAnimation
{
get => GetValue(FadeInAnimationProperty);
set => SetValue(FadeInAnimationProperty, value);
}
/// <summary>
/// Gets or sets the animation played when page disappears.
/// </summary>
public IAnimation FadeOutAnimation
{
get => GetValue(FadeOutAnimationProperty);
set => SetValue(FadeOutAnimationProperty, value);
}
/// <summary>
/// Duplicates the Content property with a private setter.
/// </summary>
public new object Content
{
get => base.Content;
private set => base.Content = value;
}
/// <summary>
/// Gets or sets the ReactiveUI view locator used by this router.
/// </summary>
@ -149,82 +93,29 @@ namespace Avalonia.ReactiveUI
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
/// <exception cref="Exception">
/// Thrown when ViewLocator is unable to find the appropriate view.
/// </exception>
private void NavigateToViewModel(IRoutableViewModel viewModel)
private void NavigateToViewModel(object viewModel)
{
if (viewModel == null)
{
this.Log().Info("ViewModel is null, falling back to default content.");
UpdateContent(DefaultContent);
this.Log().Info("ViewModel is null. Falling back to default content.");
Content = DefaultContent;
return;
}
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
var view = viewLocator.ResolveView(viewModel);
if (view == null) throw new Exception($"Couldn't find view for '{viewModel}'. Is it registered?");
var viewInstance = viewLocator.ResolveView(viewModel);
if (viewInstance == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
Content = DefaultContent;
return;
}
this.Log().Info($"Ready to show {view} with autowired {viewModel}.");
view.ViewModel = viewModel;
if (view is IStyledElement styled)
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
viewInstance.ViewModel = viewModel;
if (viewInstance is IStyledElement styled)
styled.DataContext = viewModel;
UpdateContent(view);
}
/// <summary>
/// Updates the content with transitions.
/// </summary>
/// <param name="newContent">New content to set.</param>
private async void UpdateContent(object newContent)
{
if (FadeOutAnimation != null)
await FadeOutAnimation.RunAsync(this, Clock);
Content = newContent;
if (FadeInAnimation != null)
await FadeInAnimation.RunAsync(this, Clock);
}
/// <summary>
/// Creates opacity animation for this routed view host.
/// </summary>
/// <param name="from">Opacity to start from.</param>
/// <param name="to">Opacity to finish with.</param>
/// <param name="duration">Duration of the animation.</param>
/// <returns>Animation object instance.</returns>
private static IAnimation CreateOpacityAnimation(double from, double to, TimeSpan duration)
{
return new Avalonia.Animation.Animation
{
Duration = duration,
Children =
{
new KeyFrame
{
Setters =
{
new Setter
{
Property = OpacityProperty,
Value = from
}
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter
{
Property = OpacityProperty,
Value = to
}
},
Cue = new Cue(1d)
}
}
};
Content = viewInstance;
}
}
}
}

75
src/Avalonia.ReactiveUI/TransitioningContentControl.cs

@ -0,0 +1,75 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ContentControl that animates the transition when its content is changed.
/// </summary>
public class TransitioningContentControl : ContentControl, IStyleable
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="PageTransition"/> property.
/// </summary>
public static readonly AvaloniaProperty<IPageTransition> PageTransitionProperty =
AvaloniaProperty.Register<TransitioningContentControl, IPageTransition>(nameof(PageTransition),
new CrossFade(TimeSpan.FromSeconds(0.5)));
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="DefaultContent"/> property.
/// </summary>
public static readonly AvaloniaProperty<object> DefaultContentProperty =
AvaloniaProperty.Register<TransitioningContentControl, object>(nameof(DefaultContent));
/// <summary>
/// Gets or sets the animation played when content appears and disappears.
/// </summary>
public IPageTransition PageTransition
{
get => GetValue(PageTransitionProperty);
set => SetValue(PageTransitionProperty, value);
}
/// <summary>
/// Gets or sets the content displayed whenever there is no page currently routed.
/// </summary>
public object DefaultContent
{
get => GetValue(DefaultContentProperty);
set => SetValue(DefaultContentProperty, value);
}
/// <summary>
/// Gets or sets the content with animation.
/// </summary>
public new object Content
{
get => base.Content;
set => UpdateContentWithTransition(value);
}
/// <summary>
/// TransitioningContentControl uses the default ContentControl
/// template from Avalonia default theme.
/// </summary>
Type IStyleable.StyleKey => typeof(ContentControl);
/// <summary>
/// Updates the content with transitions.
/// </summary>
/// <param name="content">New content to set.</param>
private async void UpdateContentWithTransition(object content)
{
if (PageTransition != null)
await PageTransition.Start(this, null, true);
base.Content = content;
if (PageTransition != null)
await PageTransition.Start(null, this, true);
}
}
}

80
src/Avalonia.ReactiveUI/ViewModelViewHost.cs

@ -0,0 +1,80 @@
// Copyright (c) The Avalonia 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.Reactive.Disposables;
using ReactiveUI;
using Splat;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// This content control will automatically load the View associated with
/// the ViewModel property and display it. This control is very useful
/// inside a DataTemplate to display the View associated with a ViewModel.
/// </summary>
public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewModel"/> property.
/// </summary>
public static readonly AvaloniaProperty<object> ViewModelProperty =
AvaloniaProperty.Register<ViewModelViewHost, object>(nameof(ViewModel));
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelViewHost"/> class.
/// </summary>
public ViewModelViewHost()
{
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel)
.Subscribe(NavigateToViewModel)
.DisposeWith(disposables);
});
}
/// <summary>
/// Gets or sets the ViewModel to display.
/// </summary>
public object ViewModel
{
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
/// <summary>
/// Gets or sets the view locator.
/// </summary>
public IViewLocator ViewLocator { get; set; }
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
private void NavigateToViewModel(object viewModel)
{
if (viewModel == null)
{
this.Log().Info("ViewModel is null. Falling back to default content.");
Content = DefaultContent;
return;
}
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
var viewInstance = viewLocator.ResolveView(viewModel);
if (viewInstance == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
Content = DefaultContent;
return;
}
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
viewInstance.ViewModel = viewModel;
if (viewInstance is IStyledElement styled)
styled.DataContext = viewModel;
Content = viewInstance;
}
}
}

10
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@ -46,11 +46,11 @@
<SolidColorBrush x:Key="ErrorBrush" Color="{DynamicResource ErrorColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ErrorLowBrush" Color="{DynamicResource ErrorLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="Teal"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="LimeGreen"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="Orange"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="OrangeRed"/>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="#007ACC" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="#1F9E45" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="#FDB328" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="#BD202C" Opacity="0.75"/>
<Thickness x:Key="ThemeBorderThickness">1,1,1,1</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>

10
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -46,11 +46,11 @@
<SolidColorBrush x:Key="ErrorBrush" Color="{DynamicResource ErrorColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ErrorLowBrush" Color="{DynamicResource ErrorLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="Teal"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="LimeGreen"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="Orange"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="OrangeRed"/>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="#007ACC" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="#1F9E45" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="#FDB328" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="#BD202C" Opacity="0.75"/>
<Thickness x:Key="ThemeBorderThickness">1</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>

6
src/Avalonia.Themes.Default/NotificationCard.xaml

@ -13,7 +13,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="8,8,0,0">
<ContentControl MinHeight="150" Content="{TemplateBinding Content}" />
<ContentControl Name="PART_Content" Content="{TemplateBinding Content}" />
</Border>
</LayoutTransformControl>
</ControlTemplate>
@ -40,6 +40,10 @@
</Style.Animations>
</Style>
<Style Selector="NotificationCard/template/ ContentControl#PART_Content">
<Setter Property="MinHeight" Value="150" />
</Style>
<Style Selector="NotificationCard[IsClosing=true] /template/ LayoutTransformControl#PART_LayoutTransformControl">
<Setter Property="RenderTransformOrigin" Value="50%,0%"/>
<Style.Animations>

8
src/Avalonia.Visuals/Animation/CrossFade.cs

@ -14,8 +14,8 @@ namespace Avalonia.Animation
/// </summary>
public class CrossFade : IPageTransition
{
private Animation _fadeOutAnimation;
private Animation _fadeInAnimation;
private readonly Animation _fadeOutAnimation;
private readonly Animation _fadeInAnimation;
/// <summary>
/// Initializes a new instance of the <see cref="CrossFade"/> class.
@ -61,10 +61,10 @@ namespace Avalonia.Animation
new Setter
{
Property = Visual.OpacityProperty,
Value = 0d
Value = 1d
}
},
Cue = new Cue(0d)
Cue = new Cue(1d)
}
}

24
src/Avalonia.X11/X11CursorFactory.cs

@ -8,6 +8,8 @@ namespace Avalonia.X11
{
class X11CursorFactory : IStandardCursorFactory
{
private static IntPtr _nullCursor;
private readonly IntPtr _display;
private Dictionary<CursorFontShape, IntPtr> _cursors;
@ -42,16 +44,34 @@ namespace Avalonia.X11
public X11CursorFactory(IntPtr display)
{
_display = display;
_nullCursor = GetNullCursor(display);
_cursors = Enum.GetValues(typeof(CursorFontShape)).Cast<CursorFontShape>()
.ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id));
}
public IPlatformHandle GetCursor(StandardCursorType cursorType)
{
var handle = s_mapping.TryGetValue(cursorType, out var shape)
IntPtr handle;
if (cursorType == StandardCursorType.None)
{
handle = _nullCursor;
}
else
{
handle = s_mapping.TryGetValue(cursorType, out var shape)
? _cursors[shape]
: _cursors[CursorFontShape.XC_top_left_arrow];
}
return new PlatformHandle(handle, "XCURSOR");
}
private static IntPtr GetNullCursor(IntPtr display)
{
XColor color = new XColor();
byte[] data = new byte[] { 0 };
IntPtr window = XLib.XRootWindow(display, 0);
IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, data, 1, 1);
return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0);
}
}
}

3
src/Avalonia.X11/XLib.cs

@ -321,6 +321,9 @@ namespace Avalonia.X11
public static extern IntPtr XCreatePixmapCursor(IntPtr display, IntPtr source, IntPtr mask,
ref XColor foreground_color, ref XColor background_color, int x_hot, int y_hot);
[DllImport(libX11)]
public static extern IntPtr XCreateBitmapFromData(IntPtr display, IntPtr drawable, byte[] data, int width, int height);
[DllImport(libX11)]
public static extern IntPtr XCreatePixmapFromBitmapData(IntPtr display, IntPtr drawable, byte[] data, int width,
int height, IntPtr fg, IntPtr bg, int depth);

3
src/Gtk/Avalonia.Gtk3/CursorFactory.cs

@ -12,6 +12,7 @@ namespace Avalonia.Gtk3
private static readonly Dictionary<StandardCursorType, object> CursorTypeMapping = new Dictionary
<StandardCursorType, object>
{
{StandardCursorType.None, CursorType.Blank},
{StandardCursorType.AppStarting, CursorType.Watch},
{StandardCursorType.Arrow, CursorType.LeftPtr},
{StandardCursorType.Cross, CursorType.Cross},
@ -80,4 +81,4 @@ namespace Avalonia.Gtk3
return rv;
}
}
}
}

1
src/Gtk/Avalonia.Gtk3/GdkCursor.cs

@ -2,6 +2,7 @@
{
enum GdkCursorType
{
Blank = -2,
CursorIsPixmap = -1,
XCursor = 0,
Arrow = 2,

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@ -1 +1 @@
Subproject commit 1e3ffc315401f0b2eb96a0e79b25c2fc19a80d78
Subproject commit a73c5234831267b23160e01a9fbc83be633f69fc

1
src/Windows/Avalonia.Win32/CursorFactory.cs

@ -41,6 +41,7 @@ namespace Avalonia.Win32
private static readonly Dictionary<StandardCursorType, int> CursorTypeMapping = new Dictionary
<StandardCursorType, int>
{
{StandardCursorType.None, 0},
{StandardCursorType.AppStarting, 32650},
{StandardCursorType.Arrow, 32512},
{StandardCursorType.Cross, 32515},

35
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -749,6 +749,40 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal("b", target.SelectedItem);
}
[Fact]
public void Mode_For_SelectedIndex_Is_TwoWay_By_Default()
{
var items = new[]
{
new Item(),
new Item(),
new Item(),
};
var vm = new MasterViewModel
{
Child = new ChildViewModel
{
Items = items,
SelectedIndex = 1,
}
};
var target = new SelectingItemsControl { DataContext = vm };
var itemsBinding = new Binding("Child.Items");
var selectedIndBinding = new Binding("Child.SelectedIndex");
target.Bind(SelectingItemsControl.ItemsProperty, itemsBinding);
target.Bind(SelectingItemsControl.SelectedIndexProperty, selectedIndBinding);
Assert.Equal(1, target.SelectedIndex);
target.SelectedIndex = 2;
Assert.Equal(2, target.SelectedIndex);
Assert.Equal(2, vm.Child.SelectedIndex);
}
private FuncControlTemplate Template()
{
return new FuncControlTemplate<SelectingItemsControl>(control =>
@ -785,6 +819,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
public IList<Item> Items { get; set; }
public Item SelectedItem { get; set; }
public int SelectedIndex { get; set; }
}
private class RootWithItems : TestRoot

9
tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs

@ -0,0 +1,9 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Xunit;
// Required to avoid InvalidOperationException sometimes thrown
// from Splat.MemoizingMRUCache.cs which is not thread-safe.
// Thrown when trying to access WhenActivated concurrently.
[assembly: CollectionBehavior(DisableTestParallelization = true)]

116
tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs

@ -0,0 +1,116 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Xunit;
using ReactiveUI;
using Avalonia.ReactiveUI;
using Avalonia.UnitTests;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.VisualTree;
using Avalonia.Controls.Presenters;
using Splat;
using System.Threading.Tasks;
using System;
namespace Avalonia.ReactiveUI.UnitTests
{
public class AutoDataTemplateBindingHookTest
{
public class NestedViewModel : ReactiveObject { }
public class NestedView : ReactiveUserControl<NestedViewModel> { }
public class ExampleViewModel : ReactiveObject
{
public ObservableCollection<NestedViewModel> Items { get; } = new ObservableCollection<NestedViewModel>();
}
public class ExampleView : ReactiveUserControl<ExampleViewModel>
{
public ItemsControl List { get; } = new ItemsControl();
public ExampleView()
{
Content = List;
ViewModel = new ExampleViewModel();
this.OneWayBind(ViewModel, x => x.Items, x => x.List.Items);
}
}
public AutoDataTemplateBindingHookTest()
{
Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register(() => new NestedView(), typeof(IViewFor<NestedViewModel>));
}
[Fact]
public void Should_Apply_Data_Template_Binding_When_No_Template_Is_Set()
{
var view = new ExampleView();
Assert.NotNull(view.List.ItemTemplate);
}
[Fact]
public void Should_Use_View_Model_View_Host_As_Data_Template()
{
var view = new ExampleView();
view.ViewModel.Items.Add(new NestedViewModel());
view.List.Template = GetTemplate();
view.List.ApplyTemplate();
view.List.Presenter.ApplyTemplate();
var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child;
container.UpdateChild();
Assert.IsType<ViewModelViewHost>(container.Child);
}
[Fact]
public void Should_Resolve_And_Embedd_Appropriate_View_Model()
{
var view = new ExampleView();
var root = new TestRoot { Child = view };
view.ViewModel.Items.Add(new NestedViewModel());
view.List.Template = GetTemplate();
view.List.ApplyTemplate();
view.List.Presenter.ApplyTemplate();
var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child;
container.UpdateChild();
var host = (ViewModelViewHost) container.Child;
Assert.IsType<NestedViewModel>(host.ViewModel);
Assert.IsType<NestedViewModel>(host.DataContext);
host.DataContext = "changed context";
Assert.IsType<string>(host.ViewModel);
Assert.IsType<string>(host.DataContext);
}
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ItemsControl>(parent =>
{
return new Border
{
Background = new Media.SolidColorBrush(0xffffffff),
Child = new ItemsPresenter
{
Name = "PART_ItemsPresenter",
MemberSelector = parent.MemberSelector,
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
}
};
});
}
}
}

5
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@ -1,3 +1,6 @@
// Copyright (c) The Avalonia 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.Reactive.Concurrency;
using System.Reactive.Disposables;
@ -13,7 +16,7 @@ using Splat;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Avalonia
namespace Avalonia.ReactiveUI.UnitTests
{
public class AvaloniaActivationForViewFetcherTest
{

34
tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs

@ -0,0 +1,34 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
using Xunit;
namespace Avalonia.ReactiveUI.UnitTests
{
public class ReactiveUserControlTest
{
public class ExampleViewModel : ReactiveObject { }
public class ExampleView : ReactiveUserControl<ExampleViewModel> { }
[Fact]
public void Data_Context_Should_Stay_In_Sync_With_Reactive_User_Control_View_Model()
{
var view = new ExampleView();
var viewModel = new ExampleViewModel();
Assert.Null(view.ViewModel);
view.DataContext = viewModel;
Assert.Equal(view.ViewModel, viewModel);
Assert.Equal(view.DataContext, viewModel);
view.DataContext = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
}
}
}

37
tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs

@ -0,0 +1,37 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
using Xunit;
namespace Avalonia.ReactiveUI.UnitTests
{
public class ReactiveWindowTest
{
public class ExampleViewModel : ReactiveObject { }
public class ExampleWindow : ReactiveWindow<ExampleViewModel> { }
[Fact]
public void Data_Context_Should_Stay_In_Sync_With_Reactive_Window_View_Model()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var view = new ExampleWindow();
var viewModel = new ExampleViewModel();
Assert.Null(view.ViewModel);
view.DataContext = viewModel;
Assert.Equal(view.ViewModel, viewModel);
Assert.Equal(view.DataContext, viewModel);
view.DataContext = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
}
}
}
}

8
tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs

@ -1,3 +1,6 @@
// Copyright (c) The Avalonia 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.Reactive.Concurrency;
using System.Reactive.Disposables;
@ -16,7 +19,7 @@ using System.Threading.Tasks;
using System.Reactive;
using Avalonia.ReactiveUI;
namespace Avalonia
namespace Avalonia.ReactiveUI.UnitTests
{
public class RoutedViewHostTest
{
@ -59,8 +62,7 @@ namespace Avalonia
{
Router = screen.Router,
DefaultContent = defaultContent,
FadeOutAnimation = null,
FadeInAnimation = null
PageTransition = null
};
var root = new TestRoot

63
tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs

@ -0,0 +1,63 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using ReactiveUI;
using Splat;
using Xunit;
namespace Avalonia.ReactiveUI.UnitTests
{
public class TransitioningContentControlTest
{
[Fact]
public void Transitioning_Control_Should_Derive_Template_From_Content_Control()
{
var target = new TransitioningContentControl();
var stylable = (IStyledElement)target;
Assert.Equal(typeof(ContentControl),stylable.StyleKey);
}
[Fact]
public void Transitioning_Control_Template_Should_Be_Instantiated()
{
var target = new TransitioningContentControl
{
PageTransition = null,
Template = GetTemplate(),
Content = "Foo"
};
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
var child = ((IVisual)target).VisualChildren.Single();
Assert.IsType<Border>(child);
child = child.VisualChildren.Single();
Assert.IsType<ContentPresenter>(child);
child = child.VisualChildren.Single();
Assert.IsType<TextBlock>(child);
}
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ContentControl>(parent =>
{
return new Border
{
Background = new Media.SolidColorBrush(0xffffffff),
Child = new ContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty],
}
};
});
}
}
}

74
tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs

@ -0,0 +1,74 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
using Xunit;
namespace Avalonia.ReactiveUI.UnitTests
{
public class ViewModelViewHostTest
{
public class FirstViewModel { }
public class FirstView : ReactiveUserControl<FirstViewModel> { }
public class SecondViewModel : ReactiveObject { }
public class SecondView : ReactiveUserControl<SecondViewModel> { }
public ViewModelViewHostTest()
{
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register(() => new FirstView(), typeof(IViewFor<FirstViewModel>));
Locator.CurrentMutable.Register(() => new SecondView(), typeof(IViewFor<SecondViewModel>));
}
[Fact]
public void ViewModelViewHost_View_Should_Stay_In_Sync_With_ViewModel()
{
var defaultContent = new TextBlock();
var host = new ViewModelViewHost
{
DefaultContent = defaultContent,
PageTransition = null
};
var root = new TestRoot
{
Child = host
};
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
var first = new FirstViewModel();
host.ViewModel = first;
Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstView), host.Content.GetType());
Assert.Equal(first, ((FirstView)host.Content).DataContext);
Assert.Equal(first, ((FirstView)host.Content).ViewModel);
var second = new SecondViewModel();
host.ViewModel = second;
Assert.NotNull(host.Content);
Assert.Equal(typeof(SecondView), host.Content.GetType());
Assert.Equal(second, ((SecondView)host.Content).DataContext);
Assert.Equal(second, ((SecondView)host.Content).ViewModel);
host.ViewModel = null;
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
host.ViewModel = first;
Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstView), host.Content.GetType());
Assert.Equal(first, ((FirstView)host.Content).DataContext);
Assert.Equal(first, ((FirstView)host.Content).ViewModel);
}
}
}
Loading…
Cancel
Save