Browse Source

Merge branch 'master' into fixes/directional-navigation

pull/1812/head
danwalmsley 8 years ago
committed by GitHub
parent
commit
1f222c109c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/Microsoft.CSharp.props
  2. 2
      build/Microsoft.Reactive.Testing.props
  3. 2
      build/ReactiveUI.props
  4. 10
      build/Rx.props
  5. 13
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  6. 13
      src/Avalonia.Base/Threading/Dispatcher.cs
  7. 11
      src/Avalonia.Base/Threading/IDispatcher.cs
  8. 168
      src/Avalonia.Base/Threading/JobRunner.cs
  9. 161
      src/Avalonia.Controls/Primitives/UniformGrid.cs
  10. 176
      src/Avalonia.Controls/RadioButton.cs
  11. 7
      src/Windows/Avalonia.Win32/Win32Platform.cs
  12. 144
      tests/Avalonia.Controls.UnitTests/Primitives/UniformGridTests.cs
  13. 38
      tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs
  14. 6
      tests/Avalonia.UnitTests/ImmediateDispatcher.cs

2
build/Microsoft.CSharp.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.3.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/Microsoft.Reactive.Testing.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Reactive.Testing" Version="3.0.0" /> <PackageReference Include="Microsoft.Reactive.Testing" Version="4.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="reactiveui" Version="8.0.0" /> <PackageReference Include="reactiveui" Version="8.7.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

10
build/Rx.props

@ -1,9 +1,9 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Reactive" Version="3.1.1" /> <PackageReference Include="System.Reactive" Version="4.0.0" />
<PackageReference Include="System.Reactive.Core" Version="3.1.1" /> <PackageReference Include="System.Reactive.Core" Version="4.0.0" />
<PackageReference Include="System.Reactive.Interfaces" Version="3.1.1" /> <PackageReference Include="System.Reactive.Interfaces" Version="4.0.0" />
<PackageReference Include="System.Reactive.Linq" Version="3.1.1" /> <PackageReference Include="System.Reactive.Linq" Version="4.0.0" />
<PackageReference Include="System.Reactive.PlatformServices" Version="3.1.1" /> <PackageReference Include="System.Reactive.PlatformServices" Version="4.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

13
samples/ControlCatalog/Pages/RadioButtonPage.xaml

@ -22,6 +22,19 @@
<RadioButton IsChecked="{x:Null}" IsThreeState="True">Three States: Option 3</RadioButton> <RadioButton IsChecked="{x:Null}" IsThreeState="True">Three States: Option 3</RadioButton>
<RadioButton IsChecked="{x:Null}" IsThreeState="True" IsEnabled="False">Disabled</RadioButton> <RadioButton IsChecked="{x:Null}" IsThreeState="True" IsEnabled="False">Disabled</RadioButton>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical"
Spacing="16">
<RadioButton GroupName="A" IsChecked="True">Group A: Option 1</RadioButton>
<RadioButton GroupName="A" IsEnabled="False">Group A: Disabled</RadioButton>
<RadioButton GroupName="B">Group B: Option 1</RadioButton>
<RadioButton GroupName="B" IsChecked="{x:Null}">Group B: Option 3</RadioButton>
</StackPanel>
<StackPanel Orientation="Vertical"
Spacing="16">
<RadioButton GroupName="A" IsChecked="True">Group A: Option 2</RadioButton>
<RadioButton GroupName="B">Group B: Option 2</RadioButton>
<RadioButton GroupName="B" IsChecked="{x:Null}">Group B: Option 4</RadioButton>
</StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

13
src/Avalonia.Base/Threading/Dispatcher.cs

@ -69,7 +69,7 @@ namespace Avalonia.Threading
/// </summary> /// </summary>
public void RunJobs() public void RunJobs()
{ {
_jobRunner?.RunJobs(null); _jobRunner.RunJobs(null);
} }
/// <summary> /// <summary>
@ -82,14 +82,21 @@ namespace Avalonia.Threading
public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{ {
Contract.Requires<ArgumentNullException>(action != null); Contract.Requires<ArgumentNullException>(action != null);
return _jobRunner?.InvokeAsync(action, priority); return _jobRunner.InvokeAsync(action, priority);
}
/// <inheritdoc/>
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal)
{
Contract.Requires<ArgumentNullException>(function != null);
return _jobRunner.InvokeAsync(function, priority);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{ {
Contract.Requires<ArgumentNullException>(action != null); Contract.Requires<ArgumentNullException>(action != null);
_jobRunner?.Post(action, priority); _jobRunner.Post(action, priority);
} }
/// <summary> /// <summary>

11
src/Avalonia.Base/Threading/IDispatcher.cs

@ -28,12 +28,17 @@ namespace Avalonia.Threading
void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal); void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary> /// <summary>
/// Post action that will be invoked on main thread /// Posts an action that will be invoked on the dispatcher thread.
/// </summary> /// </summary>
/// <param name="action">The method.</param> /// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param> /// <param name="priority">The priority with which to invoke the method.</param>
// TODO: The naming of this method is confusing: the Async suffix usually means return a task.
// Remove this and rename InvokeTaskAsync as InvokeAsync. See #816.
Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal); Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary>
/// Posts a function that will be invoked on the dispatcher thread.
/// </summary>
/// <param name="function">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal);
} }
} }

168
src/Avalonia.Base/Threading/JobRunner.cs

@ -14,32 +14,16 @@ namespace Avalonia.Threading
/// </summary> /// </summary>
internal class JobRunner internal class JobRunner
{ {
private IPlatformThreadingInterface _platform; private IPlatformThreadingInterface _platform;
private Queue<Job>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1) private readonly Queue<IJob>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1)
.Select(_ => new Queue<Job>()).ToArray(); .Select(_ => new Queue<IJob>()).ToArray();
public JobRunner(IPlatformThreadingInterface platform) public JobRunner(IPlatformThreadingInterface platform)
{ {
_platform = platform; _platform = platform;
} }
Job GetNextJob(DispatcherPriority minimumPriority)
{
for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--)
{
var q = _queues[c];
lock (q)
{
if (q.Count > 0)
return q.Dequeue();
}
}
return null;
}
/// <summary> /// <summary>
/// Runs continuations pushed on the loop. /// Runs continuations pushed on the loop.
/// </summary> /// </summary>
@ -52,24 +36,8 @@ namespace Avalonia.Threading
var job = GetNextJob(minimumPriority); var job = GetNextJob(minimumPriority);
if (job == null) if (job == null)
return; return;
if (job.TaskCompletionSource == null) job.Run();
{
job.Action();
}
else
{
try
{
job.Action();
job.TaskCompletionSource.SetResult(null);
}
catch (Exception e)
{
job.TaskCompletionSource.SetException(e);
}
}
} }
} }
@ -83,7 +51,20 @@ namespace Avalonia.Threading
{ {
var job = new Job(action, priority, false); var job = new Job(action, priority, false);
AddJob(job); AddJob(job);
return job.TaskCompletionSource.Task; return job.Task;
}
/// <summary>
/// Invokes a method on the main loop.
/// </summary>
/// <param name="function">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that can be used to track the method's execution.</returns>
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority)
{
var job = new Job<TResult>(function, priority);
AddJob(job);
return job.Task;
} }
/// <summary> /// <summary>
@ -105,9 +86,9 @@ namespace Avalonia.Threading
_platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>(); _platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
} }
private void AddJob(Job job) private void AddJob(IJob job)
{ {
var needWake = false; bool needWake;
var queue = _queues[(int) job.Priority]; var queue = _queues[(int) job.Priority];
lock (queue) lock (queue)
{ {
@ -118,38 +99,129 @@ namespace Avalonia.Threading
_platform?.Signal(job.Priority); _platform?.Signal(job.Priority);
} }
private IJob GetNextJob(DispatcherPriority minimumPriority)
{
for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--)
{
var q = _queues[c];
lock (q)
{
if (q.Count > 0)
return q.Dequeue();
}
}
return null;
}
private interface IJob
{
/// <summary>
/// Gets the job priority.
/// </summary>
DispatcherPriority Priority { get; }
/// <summary>
/// Runs the job.
/// </summary>
void Run();
}
/// <summary> /// <summary>
/// A job to run. /// A job to run.
/// </summary> /// </summary>
private class Job private sealed class Job : IJob
{ {
/// <summary>
/// The method to call.
/// </summary>
private readonly Action _action;
/// <summary>
/// The task completion source.
/// </summary>
private readonly TaskCompletionSource<object> _taskCompletionSource;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Job"/> class. /// Initializes a new instance of the <see cref="Job"/> class.
/// </summary> /// </summary>
/// <param name="action">The method to call.</param> /// <param name="action">The method to call.</param>
/// <param name="priority">The job priority.</param> /// <param name="priority">The job priority.</param>
/// <param name="throwOnUiThread">Do not wrap excepption in TaskCompletionSource</param> /// <param name="throwOnUiThread">Do not wrap exception in TaskCompletionSource</param>
public Job(Action action, DispatcherPriority priority, bool throwOnUiThread) public Job(Action action, DispatcherPriority priority, bool throwOnUiThread)
{ {
Action = action; _action = action;
Priority = priority; Priority = priority;
TaskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<object>(); _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<object>();
} }
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <summary> /// <summary>
/// Gets the method to call. /// The task.
/// </summary> /// </summary>
public Action Action { get; } public Task Task => _taskCompletionSource?.Task;
/// <inheritdoc/>
void IJob.Run()
{
if (_taskCompletionSource == null)
{
_action();
return;
}
try
{
_action();
_taskCompletionSource.SetResult(null);
}
catch (Exception e)
{
_taskCompletionSource.SetException(e);
}
}
}
/// <summary>
/// A job to run.
/// </summary>
private sealed class Job<TResult> : IJob
{
private readonly Func<TResult> _function;
private readonly TaskCompletionSource<TResult> _taskCompletionSource;
/// <summary> /// <summary>
/// Gets the job priority. /// Initializes a new instance of the <see cref="Job"/> class.
/// </summary> /// </summary>
public DispatcherPriority Priority { get; } /// <param name="function">The method to call.</param>
/// <param name="priority">The job priority.</param>
public Job(Func<TResult> function, DispatcherPriority priority)
{
_function = function;
Priority = priority;
_taskCompletionSource = new TaskCompletionSource<TResult>();
}
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <summary> /// <summary>
/// Gets the task completion source. /// The task.
/// </summary> /// </summary>
public TaskCompletionSource<object> TaskCompletionSource { get; } public Task<TResult> Task => _taskCompletionSource.Task;
/// <inheritdoc/>
void IJob.Run()
{
try
{
var result = _function();
_taskCompletionSource.SetResult(result);
}
catch (Exception e)
{
_taskCompletionSource.SetException(e);
}
}
} }
} }
} }

161
src/Avalonia.Controls/Primitives/UniformGrid.cs

@ -0,0 +1,161 @@
using System;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// A <see cref="Panel"/> with uniform column and row sizes.
/// </summary>
public class UniformGrid : Panel
{
/// <summary>
/// Defines the <see cref="Rows"/> property.
/// </summary>
public static readonly StyledProperty<int> RowsProperty =
AvaloniaProperty.Register<UniformGrid, int>(nameof(Rows));
/// <summary>
/// Defines the <see cref="Columns"/> property.
/// </summary>
public static readonly StyledProperty<int> ColumnsProperty =
AvaloniaProperty.Register<UniformGrid, int>(nameof(Columns));
/// <summary>
/// Defines the <see cref="FirstColumn"/> property.
/// </summary>
public static readonly StyledProperty<int> FirstColumnProperty =
AvaloniaProperty.Register<UniformGrid, int>(nameof(FirstColumn));
private int _rows;
private int _columns;
/// <summary>
/// Specifies the row count. If set to 0, row count will be calculated automatically.
/// </summary>
public int Rows
{
get => GetValue(RowsProperty);
set => SetValue(RowsProperty, value);
}
/// <summary>
/// Specifies the column count. If set to 0, column count will be calculated automatically.
/// </summary>
public int Columns
{
get => GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
/// <summary>
/// Specifies, for the first row, the column where the items should start.
/// </summary>
public int FirstColumn
{
get => GetValue(FirstColumnProperty);
set => SetValue(FirstColumnProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
{
UpdateRowsAndColumns();
var maxWidth = 0d;
var maxHeight = 0d;
var childAvailableSize = new Size(availableSize.Width / _columns, availableSize.Height / _rows);
foreach (var child in Children)
{
child.Measure(childAvailableSize);
if (child.DesiredSize.Width > maxWidth)
{
maxWidth = child.DesiredSize.Width;
}
if (child.DesiredSize.Height > maxHeight)
{
maxHeight = child.DesiredSize.Height;
}
}
return new Size(maxWidth * _columns, maxHeight * _rows);
}
protected override Size ArrangeOverride(Size finalSize)
{
var x = FirstColumn;
var y = 0;
var width = finalSize.Width / _columns;
var height = finalSize.Height / _rows;
foreach (var child in Children)
{
if (!child.IsVisible)
{
continue;
}
child.Arrange(new Rect(x * width, y * height, width, height));
x++;
if (x >= _columns)
{
x = 0;
y++;
}
}
return finalSize;
}
private void UpdateRowsAndColumns()
{
_rows = Rows;
_columns = Columns;
if (FirstColumn >= Columns)
{
FirstColumn = 0;
}
var itemCount = FirstColumn;
foreach (var child in Children)
{
if (child.IsVisible)
{
itemCount++;
}
}
if (_rows == 0)
{
if (_columns == 0)
{
_rows = _columns = (int)Math.Ceiling(Math.Sqrt(itemCount));
}
else
{
_rows = Math.DivRem(itemCount, _columns, out int rem);
if (rem != 0)
{
_rows++;
}
}
}
else if (_columns == 0)
{
_columns = Math.DivRem(itemCount, _rows, out int rem);
if (rem != 0)
{
_columns++;
}
}
}
}
}

176
src/Avalonia.Controls/RadioButton.cs

@ -2,19 +2,119 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Rendering;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
public class RadioButton : ToggleButton public class RadioButton : ToggleButton
{ {
private class RadioButtonGroupManager
{
public static readonly RadioButtonGroupManager Default = new RadioButtonGroupManager();
static readonly ConditionalWeakTable<IRenderRoot, RadioButtonGroupManager> s_registeredVisualRoots
= new ConditionalWeakTable<IRenderRoot, RadioButtonGroupManager>();
readonly Dictionary<string, List<WeakReference<RadioButton>>> s_registeredGroups
= new Dictionary<string, List<WeakReference<RadioButton>>>();
public static RadioButtonGroupManager GetOrCreateForRoot(IRenderRoot root)
{
if (root == null)
return Default;
return s_registeredVisualRoots.GetValue(root, key => new RadioButtonGroupManager());
}
public void Add(RadioButton radioButton)
{
lock (s_registeredGroups)
{
string groupName = radioButton.GroupName;
if (!s_registeredGroups.TryGetValue(groupName, out var group))
{
group = new List<WeakReference<RadioButton>>();
s_registeredGroups.Add(groupName, group);
}
group.Add(new WeakReference<RadioButton>(radioButton));
}
}
public void Remove(RadioButton radioButton, string oldGroupName)
{
lock (s_registeredGroups)
{
if (!string.IsNullOrEmpty(oldGroupName) && s_registeredGroups.TryGetValue(oldGroupName, out var group))
{
int i = 0;
while (i < group.Count)
{
if (!group[i].TryGetTarget(out var button) || button == radioButton)
{
group.RemoveAt(i);
continue;
}
i++;
}
if (group.Count == 0)
{
s_registeredGroups.Remove(oldGroupName);
}
}
}
}
public void SetChecked(RadioButton radioButton)
{
lock (s_registeredGroups)
{
string groupName = radioButton.GroupName;
if (s_registeredGroups.TryGetValue(groupName, out var group))
{
int i = 0;
while (i < group.Count)
{
if (!group[i].TryGetTarget(out var current))
{
group.RemoveAt(i);
continue;
}
if (current != radioButton && current.IsChecked.GetValueOrDefault())
current.IsChecked = false;
i++;
}
if (group.Count == 0)
{
s_registeredGroups.Remove(groupName);
}
}
}
}
}
public static readonly DirectProperty<RadioButton, string> GroupNameProperty =
AvaloniaProperty.RegisterDirect<RadioButton, string>(
nameof(GroupName),
o => o.GroupName,
(o, v) => o.GroupName = v);
private string _groupName;
private RadioButtonGroupManager _groupManager;
public RadioButton() public RadioButton()
{ {
this.GetObservable(IsCheckedProperty).Subscribe(IsCheckedChanged); this.GetObservable(IsCheckedProperty).Subscribe(IsCheckedChanged);
} }
public string GroupName
{
get { return _groupName; }
set { SetGroupName(value); }
}
protected override void Toggle() protected override void Toggle()
{ {
if (!IsChecked.GetValueOrDefault()) if (!IsChecked.GetValueOrDefault())
@ -23,21 +123,77 @@ namespace Avalonia.Controls
} }
} }
private void IsCheckedChanged(bool? value) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
if (!string.IsNullOrEmpty(GroupName))
{
var manager = RadioButtonGroupManager.GetOrCreateForRoot(e.Root);
if (manager != _groupManager)
{
_groupManager.Remove(this, _groupName);
_groupManager = manager;
manager.Add(this);
}
}
base.OnAttachedToVisualTree(e);
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (!string.IsNullOrEmpty(GroupName) && _groupManager != null)
{
_groupManager.Remove(this, _groupName);
}
}
private void SetGroupName(string newGroupName)
{ {
var parent = this.GetVisualParent(); string oldGroupName = GroupName;
if (newGroupName != oldGroupName)
{
if (!string.IsNullOrEmpty(oldGroupName) && _groupManager != null)
{
_groupManager.Remove(this, oldGroupName);
}
_groupName = newGroupName;
if (!string.IsNullOrEmpty(newGroupName))
{
if (_groupManager == null)
{
_groupManager = RadioButtonGroupManager.GetOrCreateForRoot(this.GetVisualRoot());
}
_groupManager.Add(this);
}
}
}
if (value.GetValueOrDefault() && parent != null) private void IsCheckedChanged(bool? value)
{
string groupName = GroupName;
if (string.IsNullOrEmpty(groupName))
{ {
var siblings = parent var parent = this.GetVisualParent();
.GetVisualChildren()
.OfType<RadioButton>()
.Where(x => x != this);
foreach (var sibling in siblings) if (value.GetValueOrDefault() && parent != null)
{
var siblings = parent
.GetVisualChildren()
.OfType<RadioButton>()
.Where(x => x != this);
foreach (var sibling in siblings)
{
if (sibling.IsChecked.GetValueOrDefault())
sibling.IsChecked = false;
}
}
}
else
{
if (value.GetValueOrDefault() && _groupManager != null)
{ {
if (sibling.IsChecked.GetValueOrDefault()) _groupManager.SetChecked(this);
sibling.IsChecked = false;
} }
} }
} }

7
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -50,7 +50,12 @@ namespace Avalonia.Win32
// Declare that this process is aware of per monitor DPI // Declare that this process is aware of per monitor DPI
if (UnmanagedMethods.ShCoreAvailable) if (UnmanagedMethods.ShCoreAvailable)
{ {
UnmanagedMethods.SetProcessDpiAwareness(UnmanagedMethods.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); var osVersion = Environment.OSVersion.Version;
if (osVersion.Major > 6 || (osVersion.Major == 6 && osVersion.Minor > 2))
{
UnmanagedMethods.SetProcessDpiAwareness(UnmanagedMethods.PROCESS_DPI_AWARENESS
.PROCESS_PER_MONITOR_DPI_AWARE);
}
} }
CreateMessageWindow(); CreateMessageWindow();

144
tests/Avalonia.Controls.UnitTests/Primitives/UniformGridTests.cs

@ -0,0 +1,144 @@
using Avalonia.Controls.Primitives;
using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives
{
public class UniformGridTests
{
[Fact]
public void Grid_Columns_Equals_Rows_For_Auto_Columns_And_Rows()
{
var target = new UniformGrid()
{
Children =
{
new Border { Width = 50, Height = 70 },
new Border { Width = 30, Height = 50 },
new Border { Width = 80, Height = 90 }
}
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
// 2 * 2 grid
Assert.Equal(new Size(2 * 80, 2 * 90), target.Bounds.Size);
}
[Fact]
public void Grid_Expands_Vertically_For_Columns_With_Auto_Rows()
{
var target = new UniformGrid()
{
Columns = 2,
Children =
{
new Border { Width = 50, Height = 70 },
new Border { Width = 30, Height = 50 },
new Border { Width = 80, Height = 90 },
new Border { Width = 20, Height = 30 },
new Border { Width = 40, Height = 60 }
}
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
// 2 * 3 grid
Assert.Equal(new Size(2 * 80, 3 * 90), target.Bounds.Size);
}
[Fact]
public void Grid_Extends_For_Columns_And_First_Column_With_Auto_Rows()
{
var target = new UniformGrid()
{
Columns = 3,
FirstColumn = 2,
Children =
{
new Border { Width = 50, Height = 70 },
new Border { Width = 30, Height = 50 },
new Border { Width = 80, Height = 90 },
new Border { Width = 20, Height = 30 },
new Border { Width = 40, Height = 60 }
}
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
// 3 * 3 grid
Assert.Equal(new Size(3 * 80, 3 * 90), target.Bounds.Size);
}
[Fact]
public void Grid_Expands_Horizontally_For_Rows_With_Auto_Columns()
{
var target = new UniformGrid()
{
Rows = 2,
Children =
{
new Border { Width = 50, Height = 70 },
new Border { Width = 30, Height = 50 },
new Border { Width = 80, Height = 90 },
new Border { Width = 20, Height = 30 },
new Border { Width = 40, Height = 60 }
}
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
// 3 * 2 grid
Assert.Equal(new Size(3 * 80, 2 * 90), target.Bounds.Size);
}
[Fact]
public void Grid_Size_Is_Limited_By_Rows_And_Columns()
{
var target = new UniformGrid()
{
Columns = 2,
Rows = 2,
Children =
{
new Border { Width = 50, Height = 70 },
new Border { Width = 30, Height = 50 },
new Border { Width = 80, Height = 90 },
new Border { Width = 20, Height = 30 },
new Border { Width = 40, Height = 60 }
}
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
// 2 * 2 grid
Assert.Equal(new Size(2 * 80, 2 * 90), target.Bounds.Size);
}
[Fact]
public void Not_Visible_Children_Are_Ignored()
{
var target = new UniformGrid()
{
Children =
{
new Border { Width = 50, Height = 70 },
new Border { Width = 30, Height = 50 },
new Border { Width = 80, Height = 90, IsVisible = false },
new Border { Width = 20, Height = 30 },
new Border { Width = 40, Height = 60 }
}
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
// 2 * 2 grid
Assert.Equal(new Size(2 * 50, 2 * 70), target.Bounds.Size);
}
}
}

38
tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs

@ -32,5 +32,43 @@ namespace Avalonia.Controls.UnitTests
Assert.True(radioButton1.IsChecked); Assert.True(radioButton1.IsChecked);
Assert.Null(radioButton2.IsChecked); Assert.Null(radioButton2.IsChecked);
} }
[Fact]
public void RadioButton_In_Same_Group_Is_Unchecked()
{
var parent = new Panel();
var panel1 = new Panel();
var panel2 = new Panel();
parent.Children.Add(panel1);
parent.Children.Add(panel2);
var radioButton1 = new RadioButton();
radioButton1.GroupName = "A";
radioButton1.IsChecked = false;
var radioButton2 = new RadioButton();
radioButton2.GroupName = "A";
radioButton2.IsChecked = true;
var radioButton3 = new RadioButton();
radioButton3.GroupName = "A";
radioButton3.IsChecked = false;
panel1.Children.Add(radioButton1);
panel1.Children.Add(radioButton2);
panel2.Children.Add(radioButton3);
Assert.False(radioButton1.IsChecked);
Assert.True(radioButton2.IsChecked);
Assert.False(radioButton3.IsChecked);
radioButton3.IsChecked = true;
Assert.False(radioButton1.IsChecked);
Assert.False(radioButton2.IsChecked);
Assert.True(radioButton3.IsChecked);
}
} }
} }

6
tests/Avalonia.UnitTests/ImmediateDispatcher.cs

@ -25,6 +25,12 @@ namespace Avalonia.UnitTests
return Task.FromResult<object>(null); return Task.FromResult<object>(null);
} }
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal)
{
var result = function();
return Task.FromResult(result);
}
public void VerifyAccess() public void VerifyAccess()
{ {
} }

Loading…
Cancel
Save