committed by
GitHub
458 changed files with 13844 additions and 12709 deletions
@ -1,6 +1,6 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="SkiaSharp" Version="1.68.0" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.1" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.2" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,25 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.ItemsRepeaterPage"> |
|||
<DockPanel> |
|||
<StackPanel DockPanel.Dock="Top" Spacing="4" Margin="0 0 0 16"> |
|||
<TextBlock Classes="h1">ItemsRepeater</TextBlock> |
|||
<TextBlock Classes="h2">A data-driven collection control that incorporates a flexible layout system, custom views, and virtualization.</TextBlock> |
|||
</StackPanel> |
|||
<StackPanel DockPanel.Dock="Right" Margin="8 0"> |
|||
<ComboBox SelectedIndex="0" SelectionChanged="LayoutChanged"> |
|||
<ComboBoxItem>Stack - Vertical</ComboBoxItem> |
|||
<ComboBoxItem>Stack - Horizontal</ComboBoxItem> |
|||
<ComboBoxItem>UniformGrid - Vertical</ComboBoxItem> |
|||
<ComboBoxItem>UniformGrid - Horizontal</ComboBoxItem> |
|||
</ComboBox> |
|||
</StackPanel> |
|||
<Border BorderThickness="1" BorderBrush="{DynamicResource ThemeBorderMidBrush}" Margin="0 0 0 16"> |
|||
<ScrollViewer Name="scroller" |
|||
HorizontalScrollBarVisibility="Auto" |
|||
VerticalScrollBarVisibility="Auto"> |
|||
<ItemsRepeater Name="repeater" Items="{Binding}"/> |
|||
</ScrollViewer> |
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,71 @@ |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class ItemsRepeaterPage : UserControl |
|||
{ |
|||
private ItemsRepeater _repeater; |
|||
private ScrollViewer _scroller; |
|||
|
|||
public ItemsRepeaterPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
_repeater = this.FindControl<ItemsRepeater>("repeater"); |
|||
_scroller = this.FindControl<ScrollViewer>("scroller"); |
|||
DataContext = Enumerable.Range(1, 100000).Select(i => $"Item {i}" ).ToArray(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
private void LayoutChanged(object sender, SelectionChangedEventArgs e) |
|||
{ |
|||
if (_repeater == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var comboBox = (ComboBox)sender; |
|||
|
|||
switch (comboBox.SelectedIndex) |
|||
{ |
|||
case 0: |
|||
_scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; |
|||
_scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; |
|||
_repeater.Layout = new StackLayout { Orientation = Orientation.Vertical }; |
|||
break; |
|||
case 1: |
|||
_scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; |
|||
_scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; |
|||
_repeater.Layout = new StackLayout { Orientation = Orientation.Horizontal }; |
|||
break; |
|||
case 2: |
|||
_scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; |
|||
_scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled; |
|||
_repeater.Layout = new UniformGridLayout |
|||
{ |
|||
Orientation = Orientation.Vertical, |
|||
MinItemWidth = 200, |
|||
MinItemHeight = 200, |
|||
}; |
|||
break; |
|||
case 3: |
|||
_scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled; |
|||
_scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; |
|||
_repeater.Layout = new UniformGridLayout |
|||
{ |
|||
Orientation = Orientation.Horizontal, |
|||
MinItemWidth = 200, |
|||
MinItemHeight = 200, |
|||
}; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
<UserControl xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.TabStripPage" |
|||
xmlns="https://github.com/avaloniaui"> |
|||
<StackPanel Orientation="Vertical" Spacing="4"> |
|||
<TextBlock Classes="h1">TabStrip</TextBlock> |
|||
<TextBlock Classes="h2">A control which displays a selectable strip of tabs</TextBlock> |
|||
|
|||
<Separator Margin="0 16"/> |
|||
|
|||
<TextBlock Classes="h1">Defined in XAML</TextBlock> |
|||
<TabStrip> |
|||
<TabStripItem>Item 1</TabStripItem> |
|||
<TabStripItem>Item 2</TabStripItem> |
|||
<TabStripItem IsEnabled="False">Disabled</TabStripItem> |
|||
</TabStrip> |
|||
|
|||
<Separator Margin="0 16"/> |
|||
|
|||
<TextBlock Classes="h1">Dynamically generated</TextBlock> |
|||
<TabStrip Items="{Binding}"> |
|||
<TabStrip.Styles> |
|||
<Style Selector="TabStripItem"> |
|||
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/> |
|||
</Style> |
|||
</TabStrip.Styles> |
|||
<TabStrip.ItemTemplate> |
|||
<DataTemplate> |
|||
<TextBlock Text="{Binding Header}"/> |
|||
</DataTemplate> |
|||
</TabStrip.ItemTemplate> |
|||
</TabStrip> |
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,45 @@ |
|||
using System; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class TabStripPage : UserControl |
|||
{ |
|||
public TabStripPage() |
|||
{ |
|||
InitializeComponent(); |
|||
|
|||
DataContext = new[] |
|||
{ |
|||
new TabStripItemViewModel |
|||
{ |
|||
Header = "Item 1", |
|||
}, |
|||
new TabStripItemViewModel |
|||
{ |
|||
Header = "Item 2", |
|||
}, |
|||
new TabStripItemViewModel |
|||
{ |
|||
Header = "Disabled", |
|||
IsEnabled = false, |
|||
}, |
|||
}; |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
private class TabStripItemViewModel |
|||
{ |
|||
public string Header { get; set; } |
|||
public bool IsEnabled { get; set; } = true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Data.Converters |
|||
{ |
|||
/// <summary>
|
|||
/// A multi-value converter which calls <see cref="string.Format(string, object)"/>
|
|||
/// </summary>
|
|||
public class StringFormatMultiValueConverter : IMultiValueConverter |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="StringFormatMultiValueConverter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="format">The format string.</param>
|
|||
/// <param name="inner">
|
|||
/// An optional inner converter to be called before the format takes place.
|
|||
/// </param>
|
|||
public StringFormatMultiValueConverter(string format, IMultiValueConverter inner) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(format != null); |
|||
|
|||
Format = format; |
|||
Inner = inner; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets an inner value converter which will be called before the string format takes place.
|
|||
/// </summary>
|
|||
public IMultiValueConverter Inner { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the format string.
|
|||
/// </summary>
|
|||
public string Format { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
return Inner == null |
|||
? string.Format(culture, Format, values.ToArray()) |
|||
: string.Format(culture, Format, Inner.Convert(values, targetType, parameter, culture)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
// 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.Collections.Generic; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// Stores values with <see cref="AvaloniaProperty"/> as key.
|
|||
/// </summary>
|
|||
/// <typeparam name="TValue">Stored value type.</typeparam>
|
|||
internal sealed class AvaloniaPropertyValueStore<TValue> |
|||
{ |
|||
private Entry[] _entries; |
|||
|
|||
public AvaloniaPropertyValueStore() |
|||
{ |
|||
// The last item in the list is always int.MaxValue
|
|||
_entries = new[] { new Entry { PropertyId = int.MaxValue, Value = default } }; |
|||
} |
|||
|
|||
private (int, bool) TryFindEntry(int propertyId) |
|||
{ |
|||
if (_entries.Length <= 12) |
|||
{ |
|||
// For small lists, we use an optimized linear search. Since the last item in the list
|
|||
// is always int.MaxValue, we can skip a conditional branch in each iteration.
|
|||
// By unrolling the loop, we can skip another unconditional branch in each iteration.
|
|||
|
|||
if (_entries[0].PropertyId >= propertyId) |
|||
return (0, _entries[0].PropertyId == propertyId); |
|||
if (_entries[1].PropertyId >= propertyId) |
|||
return (1, _entries[1].PropertyId == propertyId); |
|||
if (_entries[2].PropertyId >= propertyId) |
|||
return (2, _entries[2].PropertyId == propertyId); |
|||
if (_entries[3].PropertyId >= propertyId) |
|||
return (3, _entries[3].PropertyId == propertyId); |
|||
if (_entries[4].PropertyId >= propertyId) |
|||
return (4, _entries[4].PropertyId == propertyId); |
|||
if (_entries[5].PropertyId >= propertyId) |
|||
return (5, _entries[5].PropertyId == propertyId); |
|||
if (_entries[6].PropertyId >= propertyId) |
|||
return (6, _entries[6].PropertyId == propertyId); |
|||
if (_entries[7].PropertyId >= propertyId) |
|||
return (7, _entries[7].PropertyId == propertyId); |
|||
if (_entries[8].PropertyId >= propertyId) |
|||
return (8, _entries[8].PropertyId == propertyId); |
|||
if (_entries[9].PropertyId >= propertyId) |
|||
return (9, _entries[9].PropertyId == propertyId); |
|||
if (_entries[10].PropertyId >= propertyId) |
|||
return (10, _entries[10].PropertyId == propertyId); |
|||
} |
|||
else |
|||
{ |
|||
int low = 0; |
|||
int high = _entries.Length; |
|||
int id; |
|||
|
|||
while (high - low > 3) |
|||
{ |
|||
int pivot = (high + low) / 2; |
|||
id = _entries[pivot].PropertyId; |
|||
|
|||
if (propertyId == id) |
|||
return (pivot, true); |
|||
|
|||
if (propertyId <= id) |
|||
high = pivot; |
|||
else |
|||
low = pivot + 1; |
|||
} |
|||
|
|||
do |
|||
{ |
|||
id = _entries[low].PropertyId; |
|||
|
|||
if (id == propertyId) |
|||
return (low, true); |
|||
|
|||
if (id > propertyId) |
|||
break; |
|||
|
|||
++low; |
|||
} |
|||
while (low < high); |
|||
} |
|||
|
|||
return (0, false); |
|||
} |
|||
|
|||
public bool TryGetValue(AvaloniaProperty property, out TValue value) |
|||
{ |
|||
(int index, bool found) = TryFindEntry(property.Id); |
|||
if (!found) |
|||
{ |
|||
value = default; |
|||
return false; |
|||
} |
|||
|
|||
value = _entries[index].Value; |
|||
return true; |
|||
} |
|||
|
|||
public void AddValue(AvaloniaProperty property, TValue value) |
|||
{ |
|||
Entry[] entries = new Entry[_entries.Length + 1]; |
|||
|
|||
for (int i = 0; i < _entries.Length; ++i) |
|||
{ |
|||
if (_entries[i].PropertyId > property.Id) |
|||
{ |
|||
if (i > 0) |
|||
{ |
|||
Array.Copy(_entries, 0, entries, 0, i); |
|||
} |
|||
|
|||
entries[i] = new Entry { PropertyId = property.Id, Value = value }; |
|||
Array.Copy(_entries, i, entries, i + 1, _entries.Length - i); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
_entries = entries; |
|||
} |
|||
|
|||
public void SetValue(AvaloniaProperty property, TValue value) |
|||
{ |
|||
_entries[TryFindEntry(property.Id).Item1].Value = value; |
|||
} |
|||
|
|||
public Dictionary<AvaloniaProperty, TValue> ToDictionary() |
|||
{ |
|||
var dict = new Dictionary<AvaloniaProperty, TValue>(_entries.Length - 1); |
|||
|
|||
for (int i = 0; i < _entries.Length - 1; ++i) |
|||
{ |
|||
dict.Add(AvaloniaPropertyRegistry.Instance.FindRegistered(_entries[i].PropertyId), _entries[i].Value); |
|||
} |
|||
|
|||
return dict; |
|||
} |
|||
|
|||
private struct Entry |
|||
{ |
|||
internal int PropertyId; |
|||
internal TValue Value; |
|||
} |
|||
} |
|||
} |
|||
@ -1,168 +1,122 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
// 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; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// Callback invoked when deferred setter wants to set a value.
|
|||
/// </summary>
|
|||
/// <typeparam name="TValue">Value type.</typeparam>
|
|||
/// <param name="property">Property being set.</param>
|
|||
/// <param name="backing">Backing field reference.</param>
|
|||
/// <param name="value">New value.</param>
|
|||
internal delegate void SetAndNotifyCallback<TValue>(AvaloniaProperty property, ref TValue backing, TValue value); |
|||
|
|||
/// <summary>
|
|||
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
|
|||
/// Used to fix #855.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
|
|||
class DeferredSetter<TSetRecord> |
|||
internal sealed class DeferredSetter<TSetRecord> |
|||
{ |
|||
private struct NotifyDisposable : IDisposable |
|||
private readonly SingleOrQueue<TSetRecord> _pendingValues; |
|||
private bool _isNotifying; |
|||
|
|||
public DeferredSetter() |
|||
{ |
|||
private readonly SettingStatus status; |
|||
_pendingValues = new SingleOrQueue<TSetRecord>(); |
|||
} |
|||
|
|||
internal NotifyDisposable(SettingStatus status) |
|||
{ |
|||
this.status = status; |
|||
status.Notifying = true; |
|||
} |
|||
private static void SetAndRaisePropertyChanged(AvaloniaObject source, AvaloniaProperty<TSetRecord> property, ref TSetRecord backing, TSetRecord value) |
|||
{ |
|||
var old = backing; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
status.Notifying = false; |
|||
} |
|||
backing = value; |
|||
|
|||
source.RaisePropertyChanged(property, old, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Information on current setting/notification status of a property.
|
|||
/// </summary>
|
|||
private class SettingStatus |
|||
public bool SetAndNotify( |
|||
AvaloniaObject source, |
|||
AvaloniaProperty<TSetRecord> property, |
|||
ref TSetRecord backing, |
|||
TSetRecord value) |
|||
{ |
|||
public bool Notifying { get; set; } |
|||
|
|||
private SingleOrQueue<TSetRecord> pendingValues; |
|||
|
|||
public SingleOrQueue<TSetRecord> PendingValues |
|||
if (!_isNotifying) |
|||
{ |
|||
get |
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
return pendingValues ?? (pendingValues = new SingleOrQueue<TSetRecord>()); |
|||
SetAndRaisePropertyChanged(source, property, ref backing, value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private Dictionary<AvaloniaProperty, SettingStatus> _setRecords; |
|||
private Dictionary<AvaloniaProperty, SettingStatus> SetRecords |
|||
=> _setRecords ?? (_setRecords = new Dictionary<AvaloniaProperty, SettingStatus>()); |
|||
if (!_pendingValues.Empty) |
|||
{ |
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
while (!_pendingValues.Empty) |
|||
{ |
|||
SetAndRaisePropertyChanged(source, property, ref backing, _pendingValues.Dequeue()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private SettingStatus GetOrCreateStatus(AvaloniaProperty property) |
|||
{ |
|||
if (!SetRecords.TryGetValue(property, out var status)) |
|||
{ |
|||
status = new SettingStatus(); |
|||
SetRecords.Add(property, status); |
|||
return true; |
|||
} |
|||
|
|||
return status; |
|||
_pendingValues.Enqueue(value); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Mark the property as currently notifying.
|
|||
/// </summary>
|
|||
/// <param name="property">The property to mark as notifying.</param>
|
|||
/// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns>
|
|||
private NotifyDisposable MarkNotifying(AvaloniaProperty property) |
|||
public bool SetAndNotifyCallback<TValue>(AvaloniaProperty property, SetAndNotifyCallback<TValue> setAndNotifyCallback, ref TValue backing, TValue value) |
|||
where TValue : TSetRecord |
|||
{ |
|||
Contract.Requires<InvalidOperationException>(!IsNotifying(property)); |
|||
|
|||
SettingStatus status = GetOrCreateStatus(property); |
|||
|
|||
return new NotifyDisposable(status); |
|||
} |
|||
if (!_isNotifying) |
|||
{ |
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
setAndNotifyCallback(property, ref backing, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Check if the property is currently notifying listeners.
|
|||
/// </summary>
|
|||
/// <param name="property">The property.</param>
|
|||
/// <returns>If the property is currently notifying listeners.</returns>
|
|||
private bool IsNotifying(AvaloniaProperty property) |
|||
=> SetRecords.TryGetValue(property, out var value) && value.Notifying; |
|||
if (!_pendingValues.Empty) |
|||
{ |
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
while (!_pendingValues.Empty) |
|||
{ |
|||
setAndNotifyCallback(property, ref backing, (TValue) _pendingValues.Dequeue()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Add a pending assignment for the property.
|
|||
/// </summary>
|
|||
/// <param name="property">The property.</param>
|
|||
/// <param name="value">The value to assign.</param>
|
|||
private void AddPendingSet(AvaloniaProperty property, TSetRecord value) |
|||
{ |
|||
Contract.Requires<InvalidOperationException>(IsNotifying(property)); |
|||
return true; |
|||
} |
|||
|
|||
GetOrCreateStatus(property).PendingValues.Enqueue(value); |
|||
} |
|||
_pendingValues.Enqueue(value); |
|||
|
|||
/// <summary>
|
|||
/// Checks if there are any pending assignments for the property.
|
|||
/// </summary>
|
|||
/// <param name="property">The property to check.</param>
|
|||
/// <returns>If the property has any pending assignments.</returns>
|
|||
private bool HasPendingSet(AvaloniaProperty property) |
|||
{ |
|||
return SetRecords.TryGetValue(property, out var status) && !status.PendingValues.Empty; |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the first pending assignment for the property.
|
|||
/// Disposable that marks the property as currently notifying.
|
|||
/// When disposed, marks the property as done notifying.
|
|||
/// </summary>
|
|||
/// <param name="property">The property to check.</param>
|
|||
/// <returns>The first pending assignment for the property.</returns>
|
|||
private TSetRecord GetFirstPendingSet(AvaloniaProperty property) |
|||
private readonly struct NotifyDisposable : IDisposable |
|||
{ |
|||
return GetOrCreateStatus(property).PendingValues.Dequeue(); |
|||
} |
|||
|
|||
public delegate bool SetterDelegate<TValue>(TSetRecord record, ref TValue backing, Action<Action> notifyCallback); |
|||
private readonly DeferredSetter<TSetRecord> _setter; |
|||
|
|||
/// <summary>
|
|||
/// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824
|
|||
/// </summary>
|
|||
/// <param name="property">The property to set.</param>
|
|||
/// <param name="backing">The backing field for the property</param>
|
|||
/// <param name="setterCallback">
|
|||
/// A callback that actually sets the property.
|
|||
/// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification.
|
|||
/// </param>
|
|||
/// <param name="value">The value to try to set.</param>
|
|||
public bool SetAndNotify<TValue>( |
|||
AvaloniaProperty property, |
|||
ref TValue backing, |
|||
SetterDelegate<TValue> setterCallback, |
|||
TSetRecord value) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(setterCallback != null); |
|||
if (!IsNotifying(property)) |
|||
internal NotifyDisposable(DeferredSetter<TSetRecord> setter) |
|||
{ |
|||
bool updated = false; |
|||
if (!object.Equals(value, backing)) |
|||
{ |
|||
updated = setterCallback(value, ref backing, notification => |
|||
{ |
|||
using (MarkNotifying(property)) |
|||
{ |
|||
notification(); |
|||
} |
|||
}); |
|||
} |
|||
while (HasPendingSet(property)) |
|||
{ |
|||
updated |= setterCallback(GetFirstPendingSet(property), ref backing, notification => |
|||
{ |
|||
using (MarkNotifying(property)) |
|||
{ |
|||
notification(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return updated; |
|||
_setter = setter; |
|||
_setter._isNotifying = true; |
|||
} |
|||
else if(!object.Equals(value, backing)) |
|||
|
|||
public void Dispose() |
|||
{ |
|||
AddPendingSet(property, value); |
|||
_setter._isNotifying = false; |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,106 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// A task-like operation that is guaranteed to finish continuations synchronously,
|
|||
/// can be used for parametrized one-shot events
|
|||
/// </summary>
|
|||
public struct SynchronousCompletionAsyncResult<T> : INotifyCompletion |
|||
{ |
|||
private readonly SynchronousCompletionAsyncResultSource<T> _source; |
|||
private readonly T _result; |
|||
private readonly bool _isValid; |
|||
internal SynchronousCompletionAsyncResult(SynchronousCompletionAsyncResultSource<T> source) |
|||
{ |
|||
_source = source; |
|||
_result = default; |
|||
_isValid = true; |
|||
} |
|||
|
|||
public SynchronousCompletionAsyncResult(T result) |
|||
{ |
|||
_result = result; |
|||
_source = null; |
|||
_isValid = true; |
|||
} |
|||
|
|||
static void ThrowNotInitialized() => |
|||
throw new InvalidOperationException("This SynchronousCompletionAsyncResult was not initialized"); |
|||
|
|||
public bool IsCompleted |
|||
{ |
|||
get |
|||
{ |
|||
if (!_isValid) |
|||
ThrowNotInitialized(); |
|||
return _source == null || _source.IsCompleted; |
|||
} |
|||
} |
|||
|
|||
public T GetResult() |
|||
{ |
|||
if (!_isValid) |
|||
ThrowNotInitialized(); |
|||
return _source == null ? _result : _source.Result; |
|||
} |
|||
|
|||
|
|||
public void OnCompleted(Action continuation) |
|||
{ |
|||
if (!_isValid) |
|||
ThrowNotInitialized(); |
|||
if (_source == null) |
|||
continuation(); |
|||
else |
|||
_source.OnCompleted(continuation); |
|||
} |
|||
|
|||
public SynchronousCompletionAsyncResult<T> GetAwaiter() => this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Source for incomplete SynchronousCompletionAsyncResult
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
public class SynchronousCompletionAsyncResultSource<T> |
|||
{ |
|||
private T _result; |
|||
internal bool IsCompleted { get; private set; } |
|||
public SynchronousCompletionAsyncResult<T> AsyncResult => new SynchronousCompletionAsyncResult<T>(this); |
|||
|
|||
internal T Result => IsCompleted ? |
|||
_result : |
|||
throw new InvalidOperationException("Asynchronous operation is not yet completed"); |
|||
|
|||
private List<Action> _continuations; |
|||
|
|||
internal void OnCompleted(Action continuation) |
|||
{ |
|||
if(_continuations==null) |
|||
_continuations = new List<Action>(); |
|||
_continuations.Add(continuation); |
|||
} |
|||
|
|||
public void SetResult(T result) |
|||
{ |
|||
if (IsCompleted) |
|||
throw new InvalidOperationException("Asynchronous operation is already completed"); |
|||
_result = result; |
|||
IsCompleted = true; |
|||
if(_continuations!=null) |
|||
foreach (var c in _continuations) |
|||
c(); |
|||
_continuations = null; |
|||
} |
|||
|
|||
public void TrySetResult(T result) |
|||
{ |
|||
if(IsCompleted) |
|||
return; |
|||
SetResult(result); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Controls.ApplicationLifetimes |
|||
{ |
|||
public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable |
|||
{ |
|||
private readonly Application _app; |
|||
private int _exitCode; |
|||
private CancellationTokenSource _cts; |
|||
private bool _isShuttingDown; |
|||
private HashSet<Window> _windows = new HashSet<Window>(); |
|||
|
|||
private static ClassicDesktopStyleApplicationLifetime _activeLifetime; |
|||
static ClassicDesktopStyleApplicationLifetime() |
|||
{ |
|||
Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened); |
|||
Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent); |
|||
} |
|||
|
|||
private static void WindowClosedEvent(object sender, RoutedEventArgs e) |
|||
{ |
|||
_activeLifetime?._windows.Remove((Window)sender); |
|||
_activeLifetime?.HandleWindowClosed((Window)sender); |
|||
} |
|||
|
|||
private static void OnWindowOpened(object sender, RoutedEventArgs e) |
|||
{ |
|||
_activeLifetime?._windows.Add((Window)sender); |
|||
} |
|||
|
|||
public ClassicDesktopStyleApplicationLifetime(Application app) |
|||
{ |
|||
if (_activeLifetime != null) |
|||
throw new InvalidOperationException( |
|||
"Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); |
|||
_app = app; |
|||
_activeLifetime = this; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup; |
|||
/// <inheritdoc/>
|
|||
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit; |
|||
|
|||
/// <inheritdoc/>
|
|||
public ShutdownMode ShutdownMode { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public Window MainWindow { get; set; } |
|||
|
|||
public IReadOnlyList<Window> Windows => _windows.ToList(); |
|||
|
|||
private void HandleWindowClosed(Window window) |
|||
{ |
|||
if (window == null) |
|||
return; |
|||
|
|||
if (_isShuttingDown) |
|||
return; |
|||
|
|||
if (ShutdownMode == ShutdownMode.OnLastWindowClose && _windows.Count == 0) |
|||
Shutdown(); |
|||
else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow) |
|||
Shutdown(); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
public void Shutdown(int exitCode = 0) |
|||
{ |
|||
if (_isShuttingDown) |
|||
throw new InvalidOperationException("Application is already shutting down."); |
|||
|
|||
_exitCode = exitCode; |
|||
_isShuttingDown = true; |
|||
|
|||
try |
|||
{ |
|||
foreach (var w in Windows) |
|||
w.Close(); |
|||
var e = new ControlledApplicationLifetimeExitEventArgs(exitCode); |
|||
Exit?.Invoke(this, e); |
|||
_exitCode = e.ApplicationExitCode; |
|||
} |
|||
finally |
|||
{ |
|||
_cts?.Cancel(); |
|||
_cts = null; |
|||
_isShuttingDown = false; |
|||
} |
|||
} |
|||
|
|||
|
|||
public int Start(string[] args) |
|||
{ |
|||
Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args)); |
|||
_cts = new CancellationTokenSource(); |
|||
MainWindow?.Show(); |
|||
_app.Run(_cts.Token); |
|||
Environment.ExitCode = _exitCode; |
|||
return _exitCode; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_activeLifetime == this) |
|||
_activeLifetime = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
public static class ClassicDesktopStyleApplicationLifetimeExtensions |
|||
{ |
|||
public static int StartWithClassicDesktopLifetime<T>( |
|||
this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) |
|||
where T : AppBuilderBase<T>, new() |
|||
{ |
|||
var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode}; |
|||
builder.Instance.ApplicationLifetime = lifetime; |
|||
builder.SetupWithoutStarting(); |
|||
return lifetime.Start(args); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Avalonia.Controls.ApplicationLifetimes |
|||
{ |
|||
public interface IApplicationLifetime |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Controls.ApplicationLifetimes |
|||
{ |
|||
/// <summary>
|
|||
/// Controls application lifetime in classic desktop style
|
|||
/// </summary>
|
|||
public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="ShutdownMode"/>. This property indicates whether the application is shutdown explicitly or implicitly.
|
|||
/// If <see cref="ShutdownMode"/> is set to OnExplicitShutdown the application is only closes if Shutdown is called.
|
|||
/// The default is OnLastWindowClose
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The shutdown mode.
|
|||
/// </value>
|
|||
ShutdownMode ShutdownMode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the main window of the application.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The main window.
|
|||
/// </value>
|
|||
Window MainWindow { get; set; } |
|||
|
|||
IReadOnlyList<Window> Windows { get; } |
|||
} |
|||
} |
|||
@ -1,22 +1,19 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
namespace Avalonia.Controls.ApplicationLifetimes |
|||
{ |
|||
/// <summary>
|
|||
/// Sends events about the application lifecycle.
|
|||
/// </summary>
|
|||
public interface IApplicationLifecycle |
|||
public interface IControlledApplicationLifetime : IApplicationLifetime |
|||
{ |
|||
/// <summary>
|
|||
/// Sent when the application is starting up.
|
|||
/// </summary>
|
|||
event EventHandler<StartupEventArgs> Startup; |
|||
event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup; |
|||
|
|||
/// <summary>
|
|||
/// Sent when the application is exiting.
|
|||
/// </summary>
|
|||
event EventHandler<ExitEventArgs> Exit; |
|||
|
|||
event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit; |
|||
|
|||
/// <summary>
|
|||
/// Shuts down the application and sets the exit code that is returned to the operating system when the application exits.
|
|||
/// </summary>
|
|||
@ -0,0 +1,7 @@ |
|||
namespace Avalonia.Controls.ApplicationLifetimes |
|||
{ |
|||
public interface ISingleViewApplicationLifetime : IApplicationLifetime |
|||
{ |
|||
Control MainView { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// 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.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Controls.ApplicationLifetimes |
|||
{ |
|||
/// <summary>
|
|||
/// Contains the arguments for the <see cref="IControlledApplicationLifetime.Startup"/> event.
|
|||
/// </summary>
|
|||
public class ControlledApplicationLifetimeStartupEventArgs : EventArgs |
|||
{ |
|||
public ControlledApplicationLifetimeStartupEventArgs(IEnumerable<string> args) |
|||
{ |
|||
Args = args?.ToArray() ?? Array.Empty<string>(); |
|||
} |
|||
|
|||
public string[] Args { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public static class DesktopApplicationExtensions |
|||
{ |
|||
[Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details", true)] |
|||
public static void Run(this Application app) => throw new NotSupportedException(); |
|||
|
|||
/// <summary>
|
|||
/// On desktop-style platforms runs the application's main loop until closable is closed
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details
|
|||
/// </remarks>
|
|||
public static void Run(this Application app, ICloseable closable) |
|||
{ |
|||
var cts = new CancellationTokenSource(); |
|||
closable.Closed += (s, e) => cts.Cancel(); |
|||
|
|||
app.Run(cts.Token); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// On desktop-style platforms runs the application's main loop until main window is closed
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details
|
|||
/// </remarks>
|
|||
public static void Run(this Application app, Window mainWindow) |
|||
{ |
|||
if (mainWindow == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(mainWindow)); |
|||
} |
|||
var cts = new CancellationTokenSource(); |
|||
mainWindow.Closed += (_, __) => cts.Cancel(); |
|||
if (!mainWindow.IsVisible) |
|||
{ |
|||
mainWindow.Show(); |
|||
} |
|||
app.Run(cts.Token); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// On desktop-style platforms runs the application's main loop with custom CancellationToken
|
|||
/// without setting a lifetime.
|
|||
/// </summary>
|
|||
/// <param name="token">The token to track.</param>
|
|||
public static void Run(this Application app, CancellationToken token) |
|||
{ |
|||
Dispatcher.UIThread.MainLoop(token); |
|||
} |
|||
|
|||
public static void RunWithMainWindow<TWindow>(this Application app) |
|||
where TWindow : Avalonia.Controls.Window, new() |
|||
{ |
|||
var window = new TWindow(); |
|||
window.Show(); |
|||
app.Run(window); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,9 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
public interface IScrollAnchorProvider |
|||
{ |
|||
IControl CurrentAnchor { get; } |
|||
void RegisterAnchorCandidate(IControl element); |
|||
void UnregisterAnchorCandidate(IControl element); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue