Browse Source

Merge branch 'master' into fixes/1289

pull/1643/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
805f5e571d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages.cake
  2. 4
      readme.md
  3. 4
      samples/ControlCatalog/DecoratedWindow.xaml.cs
  4. 5
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  5. 2
      src/Avalonia.Controls/AutoCompleteBox.cs
  6. 95
      src/Avalonia.Controls/Calendar/Calendar.cs
  7. 2
      src/Avalonia.Controls/Calendar/CalendarDateRange.cs
  8. 31
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  9. 2
      src/Avalonia.Controls/Calendar/DatePicker.cs
  10. 18
      src/Avalonia.Controls/Design.cs
  11. 13
      src/Avalonia.Controls/DropDownItem.cs
  12. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  13. 5
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  14. 2
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  15. 27
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  16. 16
      src/Avalonia.Controls/Primitives/Popup.cs
  17. 37
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  18. 53
      src/Avalonia.Controls/Primitives/ScrollEventType.cs
  19. 16
      src/Avalonia.Controls/TextBox.cs
  20. 3
      src/Avalonia.Controls/ToolTip.cs
  21. 18
      src/Avalonia.Controls/Window.cs
  22. 14
      src/Avalonia.Controls/WindowBase.cs
  23. 3
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  24. 4
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  25. 4
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  26. 2
      src/Avalonia.Styling/Styling/Style.cs
  27. 5
      src/Avalonia.Themes.Default/Calendar.xaml
  28. 6
      src/Avalonia.Visuals/Media/FontFamily.cs
  29. 6
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  30. 2
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  31. 2
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  32. 3
      src/Markup/Avalonia.Markup/Data/MultiBinding.cs
  33. 1
      src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
  34. 31
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  35. 8
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  36. 2
      src/Windows/Avalonia.Win32/SystemDialogImpl.cs
  37. 19
      src/Windows/Avalonia.Win32/WindowImpl.cs
  38. 206
      tests/Avalonia.Controls.UnitTests/CarouselTests.cs
  39. 59
      tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs
  40. 53
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

2
packages.cake

@ -120,6 +120,7 @@ public class Packages
var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1;
var SharpDXDirect3D9Version = packageVersions["SharpDX.Direct3D9"].FirstOrDefault().Item1;
var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1;
var SystemComponentModelAnnotationsVersion = packageVersions["System.ComponentModel.Annotations"].FirstOrDefault().Item1;
context.Information("Package: Serilog, version: {0}", SerilogVersion);
context.Information("Package: Sprache, version: {0}", SpracheVersion);
@ -238,6 +239,7 @@ public class Packages
new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version },
new NuSpecDependency() { Id = "System.ComponentModel.Annotations", Version = SystemComponentModelAnnotationsVersion },
//.NET Core
new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp2.0", Version = "4.3.0" },
new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp2.0", Version = "1.1.0" },

4
readme.md

@ -18,7 +18,7 @@ Avalonia is a WPF-inspired cross-platform XAML-based UI framework providing a fl
## Getting Started
Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started. After installing it, open "New Project" dialog in Visual Studio, choose "Avalonia" in "Visual C#" section, select "Avalonia .NET Core Application" and press OK (<a href="http://avaloniaui.net/tutorial/images/add-dialogs.png">screenshot</a>). Now you can write code and markup that will work on multiple platforms!
Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started. After installing it, open "New Project" dialog in Visual Studio, choose "Avalonia" in "Visual C#" section, select "Avalonia .NET Core Application" and press OK (<a href="http://avaloniaui.net/docs/quickstart/images/new-project-dialog.png">screenshot</a>). Now you can write code and markup that will work on multiple platforms!
Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed))
@ -52,7 +52,7 @@ Please read the [contribution guidelines](http://avaloniaui.net/contributing/con
### Contributors
This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)].
<a href="graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
<a href="https://github.com/AvaloniaUI/Avalonia/graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
### Backers

4
samples/ControlCatalog/DecoratedWindow.xaml.cs

@ -20,7 +20,7 @@ namespace ControlCatalog
ctl.Cursor = new Cursor(cursor);
ctl.PointerPressed += delegate
{
PlatformImpl.BeginResizeDrag(edge);
PlatformImpl?.BeginResizeDrag(edge);
};
}
@ -29,7 +29,7 @@ namespace ControlCatalog
AvaloniaXamlLoader.Load(this);
this.FindControl<Control>("TitleBar").PointerPressed += delegate
{
PlatformImpl.BeginMoveDrag();
PlatformImpl?.BeginMoveDrag();
};
SetupSide("Left", StandardCursorType.LeftSide, WindowEdge.West);
SetupSide("Right", StandardCursorType.RightSide, WindowEdge.East);

5
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@ -110,7 +110,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
//Not supported
}
public void SetTopmost(bool value)
{
//Not supported
}
}
}

2
src/Avalonia.Controls/AutoCompleteBox.cs

@ -2150,7 +2150,7 @@ namespace Avalonia.Controls
}
// Update the view
if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace)
if ((e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) && e.OldItems != null)
{
for (int index = 0; index < e.OldItems.Count; index++)
{

95
src/Avalonia.Controls/Calendar/Calendar.cs

@ -769,7 +769,7 @@ namespace Avalonia.Controls
}
private static void UpdateDisplayDate(Calendar c, DateTime addedDate, DateTime removedDate)
{
Debug.Assert(c != null, "c should not be null!");
Contract.Requires<ArgumentNullException>(c != null);
// If DisplayDate < DisplayDateStart, DisplayDate = DisplayDateStart
if (DateTime.Compare(addedDate, c.DisplayDateRangeStart) < 0)
@ -1016,8 +1016,6 @@ namespace Avalonia.Controls
internal CalendarDayButton FindDayButtonFromDay(DateTime day)
{
CalendarDayButton b;
DateTime? d;
CalendarItem monthControl = MonthControl;
// REMOVE_RTM: should be updated if we support MultiCalendar
@ -1028,14 +1026,16 @@ namespace Avalonia.Controls
{
for (int childIndex = ColumnsPerMonth; childIndex < count; childIndex++)
{
b = monthControl.MonthView.Children[childIndex] as CalendarDayButton;
d = b.DataContext as DateTime?;
if (d.HasValue)
if (monthControl.MonthView.Children[childIndex] is CalendarDayButton b)
{
if (DateTimeHelper.CompareDays(d.Value, day) == 0)
var d = b.DataContext as DateTime?;
if (d.HasValue)
{
return b;
if (DateTimeHelper.CompareDays(d.Value, day) == 0)
{
return b;
}
}
}
}
@ -1044,20 +1044,6 @@ namespace Avalonia.Controls
return null;
}
private void Calendar_SizeChanged(object sender, EventArgs e)
{
Debug.Assert(sender is Calendar, "The sender should be a Calendar!");
var size = Bounds.Size;
RectangleGeometry rg = new RectangleGeometry();
rg.Rect = new Rect(0, 0, size.Width, size.Height);
if (Root != null)
{
Root.Clip = rg;
}
}
private void OnSelectedMonthChanged(DateTime? selectedMonth)
{
if (selectedMonth.HasValue)
@ -1090,7 +1076,6 @@ namespace Avalonia.Controls
internal void ResetStates()
{
CalendarDayButton d;
CalendarItem monthControl = MonthControl;
int count = RowsPerMonth * ColumnsPerMonth;
if (monthControl != null)
@ -1099,7 +1084,7 @@ namespace Avalonia.Controls
{
for (int childIndex = ColumnsPerMonth; childIndex < count; childIndex++)
{
d = monthControl.MonthView.Children[childIndex] as CalendarDayButton;
var d = (CalendarDayButton)monthControl.MonthView.Children[childIndex];
d.IgnoreMouseOverState();
}
}
@ -1190,8 +1175,6 @@ namespace Avalonia.Controls
if (HoverEnd != null && HoverStart != null)
{
int startIndex, endIndex, i;
CalendarDayButton b;
DateTime? d;
CalendarItem monthControl = MonthControl;
// This assumes a contiguous set of dates:
@ -1201,18 +1184,20 @@ namespace Avalonia.Controls
for (i = startIndex; i <= endIndex; i++)
{
b = monthControl.MonthView.Children[i] as CalendarDayButton;
b.IsSelected = true;
d = b.DataContext as DateTime?;
if (d.HasValue && DateTimeHelper.CompareDays(HoverEnd.Value, d.Value) == 0)
if (monthControl.MonthView.Children[i] is CalendarDayButton b)
{
if (FocusButton != null)
b.IsSelected = true;
var d = b.DataContext as DateTime?;
if (d.HasValue && DateTimeHelper.CompareDays(HoverEnd.Value, d.Value) == 0)
{
FocusButton.IsCurrent = false;
if (FocusButton != null)
{
FocusButton.IsCurrent = false;
}
b.IsCurrent = HasFocusInternal;
FocusButton = b;
}
b.IsCurrent = HasFocusInternal;
FocusButton = b;
}
}
}
@ -1228,8 +1213,6 @@ namespace Avalonia.Controls
if (HoverEnd != null && HoverStart != null)
{
CalendarItem monthControl = MonthControl;
CalendarDayButton b;
DateTime? d;
if (HoverEndIndex != null && HoverStartIndex != null)
{
@ -1240,15 +1223,17 @@ namespace Avalonia.Controls
{
for (i = startIndex; i <= endIndex; i++)
{
b = monthControl.MonthView.Children[i] as CalendarDayButton;
d = b.DataContext as DateTime?;
if (d.HasValue)
if (monthControl.MonthView.Children[i] is CalendarDayButton b)
{
if (!SelectedDates.Contains(d.Value))
var d = b.DataContext as DateTime?;
if (d.HasValue)
{
b.IsSelected = false;
}
if (!SelectedDates.Contains(d.Value))
{
b.IsSelected = false;
}
}
}
}
}
@ -1257,7 +1242,7 @@ namespace Avalonia.Controls
// It is SingleRange
for (i = startIndex; i <= endIndex; i++)
{
(monthControl.MonthView.Children[i] as CalendarDayButton).IsSelected = false;
((CalendarDayButton)monthControl.MonthView.Children[i]).IsSelected = false;
}
}
}
@ -1628,16 +1613,15 @@ namespace Avalonia.Controls
e.Handled = true;
}
}
internal void Calendar_KeyDown(object sender, KeyEventArgs e)
{
Calendar c = sender as Calendar;
Debug.Assert(c != null, "c should not be null!");
if (!e.Handled && c.IsEnabled)
internal void Calendar_KeyDown(KeyEventArgs e)
{
if (!e.Handled && IsEnabled)
{
e.Handled = ProcessCalendarKey(e);
}
}
internal bool ProcessCalendarKey(KeyEventArgs e)
{
if (DisplayMode == CalendarMode.Month)
@ -1976,7 +1960,7 @@ namespace Avalonia.Controls
}
}
}
private void Calendar_KeyUp(object sender, KeyEventArgs e)
private void Calendar_KeyUp(KeyEventArgs e)
{
if (!e.Handled && (e.Key == Key.LeftShift || e.Key == Key.RightShift))
{
@ -2083,6 +2067,9 @@ namespace Avalonia.Controls
DisplayDateProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateChanged);
DisplayDateStartProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateStartChanged);
DisplayDateEndProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateEndChanged);
KeyDownEvent.AddClassHandler<Calendar>(x => x.Calendar_KeyDown);
KeyUpEvent.AddClassHandler<Calendar>(x => x.Calendar_KeyUp);
}
/// <summary>
@ -2122,10 +2109,6 @@ namespace Avalonia.Controls
month.Owner = this;
}
}
LayoutUpdated += Calendar_SizeChanged;
KeyDown += Calendar_KeyDown;
KeyUp += Calendar_KeyUp;
}
}

2
src/Avalonia.Controls/Calendar/CalendarDateRange.cs

@ -66,7 +66,7 @@ namespace Avalonia.Controls
/// <returns>Inherited code: Requires comment 2.</returns>
internal bool ContainsAny(CalendarDateRange range)
{
Debug.Assert(range != null, "range should not be null!");
Contract.Requires<ArgumentNullException>(range != null);
int start = DateTime.Compare(Start, range.Start);

31
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -517,7 +517,7 @@ namespace Avalonia.Controls.Primitives
for (int childIndex = Calendar.ColumnsPerMonth; childIndex < count; childIndex++)
{
CalendarDayButton childButton = MonthView.Children[childIndex] as CalendarDayButton;
Debug.Assert(childButton != null, "childButton should not be null!");
Contract.Requires<ArgumentNullException>(childButton != null);
childButton.Index = childIndex;
SetButtonState(childButton, dateToAdd);
@ -554,7 +554,7 @@ namespace Avalonia.Controls.Primitives
for (int i = childIndex; i < count; i++)
{
childButton = MonthView.Children[i] as CalendarDayButton;
Debug.Assert(childButton != null, "childButton should not be null!");
Contract.Requires<ArgumentNullException>(childButton != null);
// button needs a content to occupy the necessary space
// for the content presenter
childButton.Content = i.ToString(DateTimeHelper.GetCurrentDateFormat());
@ -650,7 +650,7 @@ namespace Avalonia.Controls.Primitives
foreach (object child in YearView.Children)
{
CalendarButton childButton = child as CalendarButton;
Debug.Assert(childButton != null, "childButton should not be null!");
Contract.Requires<ArgumentNullException>(childButton != null);
// There should be no time component. Time is 12:00 AM
DateTime day = new DateTime(_currentMonth.Year, count + 1, 1);
childButton.DataContext = day;
@ -746,7 +746,7 @@ namespace Avalonia.Controls.Primitives
foreach (object child in YearView.Children)
{
CalendarButton childButton = child as CalendarButton;
Debug.Assert(childButton != null, "childButton should not be null!");
Contract.Requires<ArgumentNullException>(childButton != null);
year = decade + count;
if (year <= DateTime.MaxValue.Year && year >= DateTime.MinValue.Year)
@ -826,7 +826,7 @@ namespace Avalonia.Controls.Primitives
{
Owner.Focus();
}
Button b = sender as Button;
Button b = (Button)sender;
DateTime d;
if (b.IsEnabled)
@ -863,7 +863,7 @@ namespace Avalonia.Controls.Primitives
Owner.Focus();
}
Button b = sender as Button;
Button b = (Button)sender;
if (b.IsEnabled)
{
Owner.OnPreviousClick();
@ -878,7 +878,7 @@ namespace Avalonia.Controls.Primitives
{
Owner.Focus();
}
Button b = sender as Button;
Button b = (Button)sender;
if (b.IsEnabled)
{
@ -891,8 +891,7 @@ namespace Avalonia.Controls.Primitives
{
if (Owner != null)
{
CalendarDayButton b = sender as CalendarDayButton;
if (_isMouseLeftButtonDown && b != null && b.IsEnabled && !b.IsBlackout)
if (_isMouseLeftButtonDown && sender is CalendarDayButton b && b.IsEnabled && !b.IsBlackout)
{
// Update the states of all buttons to be selected starting
// from HoverStart to b
@ -918,7 +917,7 @@ namespace Avalonia.Controls.Primitives
Debug.Assert(b.DataContext != null, "The DataContext should not be null!");
Owner.UnHighlightDays();
Owner.HoverEndIndex = b.Index;
Owner.HoverEnd = (DateTime)b.DataContext;
Owner.HoverEnd = (DateTime?)b.DataContext;
// Update the States of the buttons
Owner.HighlightDays();
return;
@ -931,7 +930,7 @@ namespace Avalonia.Controls.Primitives
{
if (_isMouseLeftButtonDown)
{
CalendarDayButton b = sender as CalendarDayButton;
CalendarDayButton b = (CalendarDayButton)sender;
// The button is in Pressed state. Change the state to normal.
if (e.Device.Captured == b)
e.Device.Capture(null);
@ -973,7 +972,7 @@ namespace Avalonia.Controls.Primitives
if (b.IsEnabled && !b.IsBlackout)
{
DateTime selectedDate = (DateTime)b.DataContext;
Debug.Assert(selectedDate != null, "selectedDate should not be null!");
Contract.Requires<ArgumentNullException>(selectedDate != null);
_isMouseLeftButtonDown = true;
// null check is added for unit tests
if (e != null)
@ -1149,7 +1148,7 @@ namespace Avalonia.Controls.Primitives
if (_isControlPressed && Owner.SelectionMode == CalendarSelectionMode.MultipleRange)
{
CalendarDayButton b = sender as CalendarDayButton;
Debug.Assert(b != null, "The sender should be a non-null CalendarDayButton!");
Contract.Requires<ArgumentNullException>(b != null);
if (b.IsSelected)
{
@ -1169,7 +1168,7 @@ namespace Avalonia.Controls.Primitives
private void Month_CalendarButtonMouseDown(object sender, PointerPressedEventArgs e)
{
CalendarButton b = sender as CalendarButton;
Debug.Assert(b != null, "The sender should be a non-null CalendarDayButton!");
Contract.Requires<ArgumentNullException>(b != null);
_isMouseLeftButtonDownYearView = true;
@ -1208,7 +1207,7 @@ namespace Avalonia.Controls.Primitives
if (_isMouseLeftButtonDownYearView)
{
CalendarButton b = sender as CalendarButton;
Debug.Assert(b != null, "The sender should be a non-null CalendarDayButton!");
Contract.Requires<ArgumentNullException>(b != null);
UpdateYearViewSelection(b);
}
}
@ -1217,7 +1216,7 @@ namespace Avalonia.Controls.Primitives
{
if (_isMouseLeftButtonDownYearView)
{
CalendarButton b = sender as CalendarButton;
CalendarButton b = (CalendarButton)sender;
// The button is in Pressed state. Change the state to normal.
if (e.Device.Captured == b)
e.Device.Capture(null);

2
src/Avalonia.Controls/Calendar/DatePicker.cs

@ -842,7 +842,7 @@ namespace Avalonia.Controls
private void Calendar_KeyDown(object sender, KeyEventArgs e)
{
Calendar c = sender as Calendar;
Debug.Assert(c != null, "The Calendar should not be null!");
Contract.Requires<ArgumentNullException>(c != null);
if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month)
{

18
src/Avalonia.Controls/Design.cs

@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using Avalonia.Styling;
namespace Avalonia.Controls
{
@ -45,23 +46,18 @@ namespace Avalonia.Controls
{
return control.GetValue(DataContextProperty);
}
static readonly ConditionalWeakTable<object, Control> Substitutes = new ConditionalWeakTable<object, Control>();
public static readonly AttachedProperty<Control> PreviewWithProperty = AvaloniaProperty
.RegisterAttached<AvaloniaObject, Control>("PreviewWith", typeof (Design));
.RegisterAttached<Style, Control>("PreviewWith", typeof (Design));
public static void SetPreviewWith(object target, Control control)
public static void SetPreviewWith(Style target, Control control)
{
Substitutes.Remove(target);
Substitutes.Add(target, control);
target.SetValue(PreviewWithProperty, control);
}
public static Control GetPreviewWith(object target)
public static Control GetPreviewWith(Style target)
{
Control rv;
Substitutes.TryGetValue(target, out rv);
return rv;
return target.GetValue(PreviewWithProperty);
}
public static void ApplyDesignModeProperties(Control target, Control source)

13
src/Avalonia.Controls/DropDownItem.cs

@ -22,14 +22,13 @@ namespace Avalonia.Controls
static DropDownItem()
{
FocusableProperty.OverrideDefaultValue<DropDownItem>(true);
IsFocusedProperty.Changed.Subscribe(x =>
{
var sender = x.Sender as IControl;
}
if (sender != null)
{
((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue);
}
public DropDownItem()
{
this.GetObservable(DropDownItem.IsFocusedProperty).Subscribe(focused =>
{
PseudoClasses.Set(":selected", focused);
});
}

2
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs

@ -57,7 +57,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
public void Dispose()
{
PlatformImpl.Dispose();
PlatformImpl?.Dispose();
}
}
}

5
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -72,6 +72,11 @@ namespace Avalonia.Platform
///
void SetMinMaxSize(Size minSize, Size maxSize);
/// <summary>
/// Sets whether this window appears on top of all other windows
/// </summary>
void SetTopmost(bool value);
/// <summary>
/// Gets platform specific display information
/// </summary>

2
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@ -62,7 +62,7 @@ namespace Avalonia.Platform
RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, _allowedEffects);
var tl = root.GetSelfAndVisualAncestors().OfType<TopLevel>().FirstOrDefault();
tl.PlatformImpl.Input(rawEvent);
tl.PlatformImpl?.Input(rawEvent);
var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers);
UpdateCursor(root, effect);

27
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@ -106,7 +106,6 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
protected override void ItemsChanged(NotifyCollectionChangedEventArgs e)
{
// TODO: Handle items changing.
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
@ -115,9 +114,35 @@ namespace Avalonia.Controls.Presenters
var generator = ItemContainerGenerator;
var containers = generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
Panel.Children.RemoveAll(containers.Select(x => x.ContainerControl));
MoveToPage(-1, SelectedIndex);
}
break;
case NotifyCollectionChangedAction.Reset:
{
var generator = ItemContainerGenerator;
var containers = generator.Containers.ToList();
generator.Clear();
Panel.Children.RemoveAll(containers.Select(x => x.ContainerControl));
var newIndex = SelectedIndex;
if(SelectedIndex < 0)
{
if(Items != null && Items.Count() > 0)
{
newIndex = 0;
}
else
{
newIndex = -1;
}
}
MoveToPage(-1, newIndex);
}
break;
}
}

16
src/Avalonia.Controls/Primitives/Popup.cs

@ -70,6 +70,12 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<bool> StaysOpenProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(StaysOpen), true);
/// <summary>
/// Defines the <see cref="Topmost"/> property.
/// </summary>
public static readonly StyledProperty<bool> TopmostProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
private bool _isOpen;
private PopupRoot _popupRoot;
private TopLevel _topLevel;
@ -84,6 +90,7 @@ namespace Avalonia.Controls.Primitives
IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false);
ChildProperty.Changed.AddClassHandler<Popup>(x => x.ChildChanged);
IsOpenProperty.Changed.AddClassHandler<Popup>(x => x.IsOpenChanged);
TopmostProperty.Changed.AddClassHandler<Popup>((p, e) => p.PopupRoot.Topmost = (bool)e.NewValue);
}
/// <summary>
@ -194,6 +201,15 @@ namespace Avalonia.Controls.Primitives
set { SetValue(StaysOpenProperty, value); }
}
/// <summary>
/// Gets or sets whether this popup appears on top of all other windows
/// </summary>
public bool Topmost
{
get { return GetValue(TopmostProperty); }
set { SetValue(TopmostProperty, value); }
}
/// <summary>
/// Gets the root of the popup window.
/// </summary>

37
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -6,9 +6,21 @@ using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Input;
namespace Avalonia.Controls.Primitives
{
public class ScrollEventArgs : EventArgs
{
public ScrollEventArgs(ScrollEventType eventType, double newValue)
{
ScrollEventType = eventType;
NewValue = newValue;
}
public double NewValue { get; private set; }
public ScrollEventType ScrollEventType { get; private set; }
}
/// <summary>
/// A scrollbar control.
/// </summary>
@ -44,6 +56,9 @@ namespace Avalonia.Controls.Primitives
{
PseudoClass(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragDelta, RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragComplete, RoutingStrategies.Bubble);
}
/// <summary>
@ -88,6 +103,8 @@ namespace Avalonia.Controls.Primitives
set { SetValue(OrientationProperty, value); }
}
public event EventHandler<ScrollEventArgs> Scroll;
/// <summary>
/// Calculates whether the scrollbar should be visible.
/// </summary>
@ -140,6 +157,8 @@ namespace Avalonia.Controls.Primitives
_pageUpButton = e.NameScope.Find<Button>("PART_PageUpButton");
_pageDownButton = e.NameScope.Find<Button>("PART_PageDownButton");
if (_lineUpButton != null)
{
_lineUpButton.Click += LineUpClick;
@ -184,21 +203,39 @@ namespace Avalonia.Controls.Primitives
private void SmallDecrement()
{
Value = Math.Max(Value - SmallChange * ViewportSize, Minimum);
OnScroll(ScrollEventType.SmallDecrement);
}
private void SmallIncrement()
{
Value = Math.Min(Value + SmallChange * ViewportSize, Maximum);
OnScroll(ScrollEventType.SmallIncrement);
}
private void LargeDecrement()
{
Value = Math.Max(Value - LargeChange * ViewportSize, Minimum);
OnScroll(ScrollEventType.LargeDecrement);
}
private void LargeIncrement()
{
Value = Math.Min(Value + LargeChange * ViewportSize, Maximum);
OnScroll(ScrollEventType.LargeIncrement);
}
private void OnThumbDragDelta(VectorEventArgs e)
{
OnScroll(ScrollEventType.ThumbTrack);
}
private void OnThumbDragComplete(VectorEventArgs e)
{
OnScroll(ScrollEventType.EndScroll);
}
protected void OnScroll(ScrollEventType scrollEventType)
{
Scroll?.Invoke(this, new ScrollEventArgs(scrollEventType, Value));
}
}
}

53
src/Avalonia.Controls/Primitives/ScrollEventType.cs

@ -0,0 +1,53 @@
// 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.
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Specifies the type of Avalonia.Controls.Primitives.ScrollBar.Scroll event
/// that occurred.
/// </summary>
public enum ScrollEventType
{
/// <summary>
/// Specifies that the Avalonia.Controls.Primitives.Thumb moved a specified
/// distance, as determined by the value of Avalonia.Controls.Primitives.RangeBase.SmallChange.
/// The Avalonia.Controls.Primitives.Thumb moved to the left for a horizontal
/// Avalonia.Controls.Primitives.ScrollBar or upward for a vertical Avalonia.Controls.Primitives.ScrollBar.
/// </summary>
SmallDecrement = 0,
/// <summary>
/// Specifies that the Avalonia.Controls.Primitives.Thumb moved a specified
/// distance, as determined by the value of Avalonia.Controls.Primitives.RangeBase.SmallChange.
/// The Avalonia.Controls.Primitives.Thumb moved to the right for a horizontal
/// Avalonia.Controls.Primitives.ScrollBar or downward for a vertical Avalonia.Controls.Primitives.ScrollBar.
/// </summary>
SmallIncrement = 1,
/// <summary>
/// Specifies that the Avalonia.Controls.Primitives.Thumb moved a specified
/// distance, as determined by the value of Avalonia.Controls.Primitives.RangeBase.LargeChange.
/// The Avalonia.Controls.Primitives.Thumb moved to the left for a horizontal
/// Avalonia.Controls.Primitives.ScrollBar or upward for a vertical Avalonia.Controls.Primitives.ScrollBar.
/// </summary>
LargeDecrement = 2,
/// <summary>
/// Specifies that the Avalonia.Controls.Primitives.Thumb moved a specified
/// distance, as determined by the value of Avalonia.Controls.Primitives.RangeBase.LargeChange.
/// The Avalonia.Controls.Primitives.Thumb moved to the right for a horizontal
/// Avalonia.Controls.Primitives.ScrollBar or downward for a vertical Avalonia.Controls.Primitives.ScrollBar.
/// </summary>
LargeIncrement = 3,
/// <summary>
/// The Avalonia.Controls.Primitives.Thumb was dragged and caused a Avalonia.UIElement.MouseMove
/// event. A Avalonia.Controls.Primitives.ScrollBar.Scroll event of this Avalonia.Controls.Primitives.ScrollEventType
/// may occur more than one time when the Avalonia.Controls.Primitives.Thumb
/// is dragged in the Avalonia.Controls.Primitives.ScrollBar.
/// </summary>
ThumbTrack = 4,
/// <summary>
/// Specifies that the Avalonia.Controls.Primitives.Thumb was dragged to a
/// new position and is now no longer being dragged by the user.
/// </summary>
EndScroll = 5
}
}

16
src/Avalonia.Controls/TextBox.cs

@ -68,6 +68,10 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(UseFloatingWatermark));
public static readonly DirectProperty<TextBox, string> NewLineProperty =
AvaloniaProperty.RegisterDirect<TextBox, string>(nameof(NewLine),
textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline);
struct UndoRedoState : IEquatable<UndoRedoState>
{
public string Text { get; }
@ -90,6 +94,7 @@ namespace Avalonia.Controls
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing;
private bool _ignoreTextChanges;
private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
static TextBox()
@ -241,6 +246,15 @@ namespace Avalonia.Controls
set { SetValue(TextWrappingProperty, value); }
}
/// <summary>
/// Gets or sets which characters are inserted when Enter is pressed. Default: <see cref="Environment.NewLine"/>
/// </summary>
public string NewLine
{
get { return _newLine; }
set { SetAndRaise(NewLineProperty, ref _newLine, value); }
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
@ -498,7 +512,7 @@ namespace Avalonia.Controls
case Key.Enter:
if (AcceptsReturn)
{
HandleTextInput("\r\n");
HandleTextInput(NewLine);
handled = true;
}

3
src/Avalonia.Controls/ToolTip.cs

@ -234,11 +234,12 @@ namespace Avalonia.Controls
{
Close();
_popup = new PopupRoot { Content = this };
_popup = new PopupRoot { Content = this, };
((ISetLogicalParent)_popup).SetParent(control);
_popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup,
GetHorizontalOffset(control), GetVerticalOffset(control));
_popup.Show();
_popup.SnapInsideScreenEdges();
}
private void Close()

18
src/Avalonia.Controls/Window.cs

@ -145,11 +145,9 @@ namespace Avalonia.Controls
: base(impl)
{
impl.Closing = HandleClosing;
impl.WindowStateChanged = HandleWindowStateChanged;
_maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
Screens = new Screens(PlatformImpl?.Screen);
if (PlatformImpl != null)
PlatformImpl.WindowStateChanged = s => WindowState = s;
}
/// <inheritdoc/>
@ -318,6 +316,20 @@ namespace Avalonia.Controls
return args.Cancel;
}
protected virtual void HandleWindowStateChanged(WindowState state)
{
WindowState = state;
if (state == WindowState.Minimized)
{
Renderer.Stop();
}
else
{
Renderer.Start();
}
}
/// <summary>
/// Hides the window but does not close it.
/// </summary>

14
src/Avalonia.Controls/WindowBase.cs

@ -38,6 +38,9 @@ namespace Avalonia.Controls
o => o.Owner,
(o, v) => o.Owner = v);
public static readonly StyledProperty<bool> TopmostProperty =
AvaloniaProperty.Register<WindowBase, bool>(nameof(Topmost));
private bool _hasExecutedInitialLayoutPass;
private bool _isActive;
private bool _ignoreVisibilityChange;
@ -52,6 +55,8 @@ namespace Avalonia.Controls
MinHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
MaxWidthProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
MaxHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
TopmostProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue));
}
public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current)
@ -124,6 +129,15 @@ namespace Avalonia.Controls
set { SetAndRaise(OwnerProperty, ref _owner, value); }
}
/// <summary>
/// Gets or sets whether this window appears on top of all other windows
/// </summary>
public bool Topmost
{
get { return GetValue(TopmostProperty); }
set { SetValue(TopmostProperty, value); }
}
/// <summary>
/// Activates the window.
/// </summary>

3
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -36,8 +36,7 @@ namespace Avalonia.DesignerSupport
var styles = loaded as Styles;
if (styles != null)
{
var substitute = Design.GetPreviewWith(styles) ??
styles.Select(Design.GetPreviewWith).FirstOrDefault(s => s != null);
var substitute = styles.OfType<Style>().Select(Design.GetPreviewWith).FirstOrDefault(s => s != null);
if (substitute != null)
{
substitute.Styles.AddRange(styles);

4
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -102,5 +102,9 @@ namespace Avalonia.DesignerSupport.Remote
public void CanResize(bool value)
{
}
public void SetTopmost(bool value)
{
}
}
}

4
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -104,6 +104,10 @@ namespace Avalonia.DesignerSupport.Remote
public void CanResize(bool value)
{
}
public void SetTopmost(bool value)
{
}
}
class ClipboardStub : IClipboard

2
src/Avalonia.Styling/Styling/Style.cs

@ -15,7 +15,7 @@ namespace Avalonia.Styling
/// <summary>
/// Defines a style.
/// </summary>
public class Style : IStyle, ISetStyleParent
public class Style : AvaloniaObject, IStyle, ISetStyleParent
{
private static Dictionary<IStyleable, List<IDisposable>> _applied =
new Dictionary<IStyleable, List<IDisposable>>();

5
src/Avalonia.Themes.Default/Calendar.xaml

@ -15,9 +15,10 @@
<Setter Property="Template">
<ControlTemplate>
<StackPanel Name="Root"
HorizontalAlignment="Center">
HorizontalAlignment="Center"
ClipToBounds="True">
<CalendarItem Name="CalendarItem"
<CalendarItem Name="CalendarItem"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"

6
src/Avalonia.Visuals/Media/FontFamily.cs

@ -77,10 +77,10 @@ namespace Avalonia.Media
/// <summary>
/// Implicit conversion of string to FontFamily
/// </summary>
/// <param name="fontFamily"></param>
public static implicit operator FontFamily(string fontFamily)
/// <param name="s"></param>
public static implicit operator FontFamily(string s)
{
return new FontFamily(fontFamily);
return Parse(s);
}
/// <summary>

6
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -261,10 +261,13 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_unmaximize(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_close(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_keep_above(GtkWindow gtkWindow, bool setting);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask);
@ -472,6 +475,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_maximize GtkWindowMaximize;
public static D.gtk_window_unmaximize GtkWindowUnmaximize;
public static D.gtk_window_close GtkWindowClose;
public static D.gtk_window_set_keep_above GtkWindowSetKeepAbove;
public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag;
public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag;
public static D.gdk_event_request_motions GdkEventRequestMotions;

2
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -416,6 +416,8 @@ namespace Avalonia.Gtk3
public void Hide() => Native.GtkWidgetHide(GtkWidget);
public void SetTopmost(bool value) => Native.GtkWindowSetKeepAbove(GtkWidget, value);
void GetGlobalPointer(out int x, out int y)
{
int mask;

2
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@ -81,7 +81,7 @@ namespace Avalonia.Gtk3
public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value);
public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value);
class EmptyDisposable : IDisposable
{

3
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@ -10,6 +10,7 @@ using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Metadata;
using JetBrains.Annotations;
namespace Avalonia.Data
{
@ -65,7 +66,7 @@ namespace Avalonia.Data
var children = Bindings.Select(x => x.Initiate(target, null));
var input = children.Select(x => x.Subject).CombineLatest().Select(x => ConvertValue(x, targetType));
var mode = Mode == BindingMode.Default ?
targetProperty.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
targetProperty?.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
switch (mode)
{

1
src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs

@ -123,6 +123,7 @@ namespace Avalonia.MonoMac
public void Hide() => Window?.OrderOut(Window);
public void SetTopmost(bool value) => Window.Level = value ? NSWindowLevel.Floating : NSWindowLevel.Normal;
public void BeginMoveDrag()
{

31
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -560,13 +560,11 @@ namespace Avalonia.Skia
}
measured = LineBreak(Text, curOff, length, _paint, constraint, out trailingnumber);
AvaloniaFormattedTextLine line = new AvaloniaFormattedTextLine();
line.TextLength = measured;
line.Start = curOff;
subString = Text.Substring(line.Start, line.TextLength);
lineWidth = _paint.MeasureText(subString);
line.Start = curOff;
line.Length = measured - trailingnumber;
line.Width = lineWidth;
line.Height = _lineHeight;
@ -575,10 +573,33 @@ namespace Avalonia.Skia
_skiaLines.Add(line);
curY += _lineHeight;
curY += mLeading;
curOff += measured;
//if this is the last line and there are trailing newline characters then
//insert a additional line
if (curOff >= length)
{
var subStringMinusNewlines = subString.TrimEnd('\n', '\r');
var lengthDiff = subString.Length - subStringMinusNewlines.Length;
if (lengthDiff > 0)
{
AvaloniaFormattedTextLine lastLine = new AvaloniaFormattedTextLine();
lastLine.TextLength = lengthDiff;
lastLine.Start = curOff - lengthDiff;
var lastLineSubString = Text.Substring(line.Start, line.TextLength);
var lastLineWidth = _paint.MeasureText(lastLineSubString);
lastLine.Length = 0;
lastLine.Width = lastLineWidth;
lastLine.Height = _lineHeight;
lastLine.Top = curY;
_skiaLines.Add(lastLine);
curY += _lineHeight;
curY += mLeading;
}
}
}
// Now convert to Avalonia data formats

8
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -78,6 +78,14 @@ namespace Avalonia.Win32.Interop
SWP_RESIZE = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER
}
public static class WindowPosZOrder
{
public static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
public static readonly IntPtr HWND_TOP = new IntPtr(0);
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
}
public enum SizeCommand
{
Restored,

2
src/Windows/Avalonia.Win32/SystemDialogImpl.cs

@ -54,7 +54,7 @@ namespace Avalonia.Win32
var fileBuffer = new char[256];
dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length);
string userSelectedExt = null;
string userSelectedExt = string.Empty;
var title = ToChars(dialog.Title);

19
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -32,6 +32,7 @@ namespace Avalonia.Win32
private bool _trackingMouse;
private bool _decorated = true;
private bool _resizable = true;
private bool _topmost = false;
private double _scaling = 1;
private WindowState _showWindowState;
private WindowState _lastWindowState;
@ -838,7 +839,7 @@ namespace Avalonia.Win32
var cx = Math.Abs(monitorInfo.rcWork.right - x);
var cy = Math.Abs(monitorInfo.rcWork.bottom - y);
SetWindowPos(_hwnd, new IntPtr(-2), x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW);
SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW);
}
}
}
@ -901,5 +902,21 @@ namespace Avalonia.Win32
_resizable = value;
}
public void SetTopmost(bool value)
{
if (value == _topmost)
{
return;
}
IntPtr hWndInsertAfter = value ? WindowPosZOrder.HWND_TOPMOST : WindowPosZOrder.HWND_NOTOPMOST;
UnmanagedMethods.SetWindowPos(_hwnd,
hWndInsertAfter,
0, 0, 0, 0,
SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE);
_topmost = value;
}
}
}

206
tests/Avalonia.Controls.UnitTests/CarouselTests.cs

@ -1,11 +1,13 @@
// 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.Collections.ObjectModel;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -50,7 +52,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Single(target.GetLogicalChildren());
var child = target.GetLogicalChildren().Single();
Assert.IsType<TextBlock>(child);
Assert.Equal("Foo", ((TextBlock)child).Text);
}
@ -93,6 +95,208 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(2, target.ItemContainerGenerator.Containers.Count());
}
[Fact]
public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Single(target.GetLogicalChildren());
var child = target.GetLogicalChildren().Single();
Assert.IsType<TextBlock>(child);
Assert.Equal("Foo", ((TextBlock)child).Text);
var newItems = items.ToList();
newItems.RemoveAt(0);
target.Items = newItems;
child = target.GetLogicalChildren().Single();
Assert.IsType<TextBlock>(child);
Assert.Equal("Bar", ((TextBlock)child).Text);
}
[Fact]
public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes_And_Virtualized()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Single(target.GetLogicalChildren());
var child = target.GetLogicalChildren().Single();
Assert.IsType<TextBlock>(child);
Assert.Equal("Foo", ((TextBlock)child).Text);
var newItems = items.ToList();
newItems.RemoveAt(0);
target.Items = newItems;
child = target.GetLogicalChildren().Single();
Assert.IsType<TextBlock>(child);
Assert.Equal("Bar", ((TextBlock)child).Text);
}
[Fact]
public void Selected_Index_Changes_To_When_Items_Assigned_Null()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Single(target.GetLogicalChildren());
var child = target.GetLogicalChildren().Single();
Assert.IsType<TextBlock>(child);
Assert.Equal("Foo", ((TextBlock)child).Text);
target.Items = null;
var numChildren = target.GetLogicalChildren().Count();
Assert.Equal(0, numChildren);
Assert.Equal(-1, target.SelectedIndex);
}
[Fact]
public void Selected_Index_Is_Maintained_Carousel_Created_With_Non_Zero_SelectedIndex()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false,
SelectedIndex = 2
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Equal("FooBar", target.SelectedItem);
var child = target.GetVisualDescendants().LastOrDefault();
Assert.IsType<TextBlock>(child);
Assert.Equal("FooBar", ((TextBlock)child).Text);
}
[Fact]
public void Selected_Item_Changes_To_Next_First_Item_When_Item_Removed_From_Beggining_Of_List()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Single(target.GetLogicalChildren());
var child = target.GetLogicalChildren().Single();
Assert.IsType<TextBlock>(child);
Assert.Equal("Foo", ((TextBlock)child).Text);
items.RemoveAt(0);
child = target.GetLogicalChildren().Single();
Assert.IsType<TextBlock>(child);
Assert.Equal("Bar", ((TextBlock)child).Text);
}
[Fact]
public void Selected_Item_Changes_To_NextAvailable_Item_If_SelectedItem_Is_Removed_From_Middle()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedIndex = 1;
items.RemoveAt(1);
Assert.Equal(1, target.SelectedIndex);
Assert.Equal("FooBar", target.SelectedItem);
}
private Control CreateTemplate(Carousel control)
{
return new CarouselPresenter

59
tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs

@ -5,6 +5,7 @@ using System;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Media;
using Xunit;
@ -59,6 +60,64 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(50, target.Value);
}
[Fact]
public void Thumb_DragDelta_Event_Should_Raise_Scroll_Event()
{
var target = new ScrollBar
{
Template = new FuncControlTemplate<ScrollBar>(Template),
};
target.ApplyTemplate();
var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
var raisedEvent = Assert.Raises<ScrollEventArgs>(
handler => target.Scroll += handler,
handler => target.Scroll -= handler,
() =>
{
var ev = new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(0, 0)
};
track.Thumb.RaiseEvent(ev);
});
Assert.Equal(ScrollEventType.ThumbTrack, raisedEvent.Arguments.ScrollEventType);
}
[Fact]
public void Thumb_DragComplete_Event_Should_Raise_Scroll_Event()
{
var target = new ScrollBar
{
Template = new FuncControlTemplate<ScrollBar>(Template),
};
target.ApplyTemplate();
var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
var raisedEvent = Assert.Raises<ScrollEventArgs>(
handler => target.Scroll += handler,
handler => target.Scroll -= handler,
() =>
{
var ev = new VectorEventArgs
{
RoutedEvent = Thumb.DragCompletedEvent,
Vector = new Vector(0, 0)
};
track.Thumb.RaiseEvent(ev);
});
Assert.Equal(ScrollEventType.EndScroll, raisedEvent.Arguments.ScrollEventType);
}
[Fact]
public void ScrollBar_Can_AutoHide()
{

53
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -247,6 +247,59 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Press_Enter_Does_Not_Accept_Return()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
AcceptsReturn = false,
Text = "1234"
};
RaiseKeyEvent(target, Key.Enter, 0);
Assert.Equal("1234", target.Text);
}
}
[Fact]
public void Press_Enter_Add_Default_Newline()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
AcceptsReturn = true
};
RaiseKeyEvent(target, Key.Enter, 0);
Assert.Equal(Environment.NewLine, target.Text);
}
}
[Fact]
public void Press_Enter_Add_Custom_Newline()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
AcceptsReturn = true,
NewLine = "Test"
};
RaiseKeyEvent(target, Key.Enter, 0);
Assert.Equal("Test", target.Text);
}
}
[Theory]
[InlineData(new object[] { false, TextWrapping.NoWrap, ScrollBarVisibility.Hidden })]
[InlineData(new object[] { false, TextWrapping.Wrap, ScrollBarVisibility.Hidden })]

Loading…
Cancel
Save