committed by
GitHub
134 changed files with 2651 additions and 1088 deletions
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using System.Text; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public class Clock : ClockBase |
|||
{ |
|||
public static IClock GlobalClock => AvaloniaLocator.Current.GetService<IGlobalClock>(); |
|||
|
|||
private IDisposable _parentSubscription; |
|||
|
|||
public Clock() |
|||
:this(GlobalClock) |
|||
{ |
|||
} |
|||
|
|||
public Clock(IClock parent) |
|||
{ |
|||
_parentSubscription = parent.Subscribe(Pulse); |
|||
} |
|||
|
|||
protected override void Stop() |
|||
{ |
|||
_parentSubscription?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using System.Text; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public class ClockBase : IClock |
|||
{ |
|||
private ClockObservable _observable; |
|||
|
|||
private IObservable<TimeSpan> _connectedObservable; |
|||
|
|||
private TimeSpan? _previousTime; |
|||
private TimeSpan _internalTime; |
|||
|
|||
protected ClockBase() |
|||
{ |
|||
_observable = new ClockObservable(); |
|||
_connectedObservable = _observable.Publish().RefCount(); |
|||
} |
|||
|
|||
protected bool HasSubscriptions => _observable.HasSubscriptions; |
|||
|
|||
public PlayState PlayState { get; set; } |
|||
|
|||
protected void Pulse(TimeSpan systemTime) |
|||
{ |
|||
if (!_previousTime.HasValue) |
|||
{ |
|||
_previousTime = systemTime; |
|||
_internalTime = TimeSpan.Zero; |
|||
} |
|||
else |
|||
{ |
|||
if (PlayState == PlayState.Pause) |
|||
{ |
|||
_previousTime = systemTime; |
|||
return; |
|||
} |
|||
var delta = systemTime - _previousTime; |
|||
_internalTime += delta.Value; |
|||
_previousTime = systemTime; |
|||
} |
|||
|
|||
_observable.Pulse(_internalTime); |
|||
|
|||
if (PlayState == PlayState.Stop) |
|||
{ |
|||
Stop(); |
|||
} |
|||
} |
|||
|
|||
protected virtual void Stop() |
|||
{ |
|||
} |
|||
|
|||
public IDisposable Subscribe(IObserver<TimeSpan> observer) |
|||
{ |
|||
return _connectedObservable.Subscribe(observer); |
|||
} |
|||
|
|||
private class ClockObservable : LightweightObservableBase<TimeSpan> |
|||
{ |
|||
public bool HasSubscriptions { get; private set; } |
|||
public void Pulse(TimeSpan time) => PublishNext(time); |
|||
protected override void Initialize() => HasSubscriptions = true; |
|||
protected override void Deinitialize() => HasSubscriptions = false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// 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; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Animation.Utils; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Data; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Manages the lifetime of animation instances as determined by its selector state.
|
|||
/// </summary>
|
|||
internal class DisposeAnimationInstanceSubject<T> : IObserver<bool>, IDisposable |
|||
{ |
|||
private IDisposable _lastInstance; |
|||
private bool _lastMatch; |
|||
private Animator<T> _animator; |
|||
private Animation _animation; |
|||
private Animatable _control; |
|||
private Action _onComplete; |
|||
private IClock _clock; |
|||
|
|||
public DisposeAnimationInstanceSubject(Animator<T> animator, Animation animation, Animatable control, IClock clock, Action onComplete) |
|||
{ |
|||
this._animator = animator; |
|||
this._animation = animation; |
|||
this._control = control; |
|||
this._onComplete = onComplete; |
|||
this._clock = clock; |
|||
} |
|||
|
|||
|
|||
public void Dispose() |
|||
{ |
|||
_lastInstance?.Dispose(); |
|||
} |
|||
|
|||
public void OnCompleted() |
|||
{ |
|||
} |
|||
|
|||
public void OnError(Exception error) |
|||
{ |
|||
_lastInstance?.Dispose(); |
|||
} |
|||
|
|||
void IObserver<bool>.OnNext(bool matchVal) |
|||
{ |
|||
if (matchVal != _lastMatch) |
|||
{ |
|||
_lastInstance?.Dispose(); |
|||
if (matchVal) |
|||
{ |
|||
_lastInstance = _animator.Run(_animation, _control, _clock, _onComplete); |
|||
} |
|||
_lastMatch = matchVal; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public interface IClock : IObservable<TimeSpan> |
|||
{ |
|||
PlayState PlayState { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public interface IGlobalClock : IClock |
|||
{ |
|||
} |
|||
} |
|||
@ -1,54 +0,0 @@ |
|||
// 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.Linq; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Provides global timing functions for animations.
|
|||
/// </summary>
|
|||
public static class Timing |
|||
{ |
|||
/// <summary>
|
|||
/// The number of frames per second.
|
|||
/// </summary>
|
|||
public const int FramesPerSecond = 60; |
|||
|
|||
/// <summary>
|
|||
/// The time span of each frame.
|
|||
/// </summary>
|
|||
internal static readonly TimeSpan FrameTick = TimeSpan.FromSeconds(1.0 / FramesPerSecond); |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="Timing"/> class.
|
|||
/// </summary>
|
|||
static Timing() |
|||
{ |
|||
var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); |
|||
|
|||
AnimationsTimer = globalTimer |
|||
.Select(_ => GetTickCount()) |
|||
.Publish() |
|||
.RefCount(); |
|||
} |
|||
|
|||
internal static TimeSpan GetTickCount() => TimeSpan.FromMilliseconds(Environment.TickCount); |
|||
|
|||
/// <summary>
|
|||
/// Gets the animation timer.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The animation timer triggers usually at 60 times per second or as
|
|||
/// defined in <see cref="FramesPerSecond"/>.
|
|||
/// The parameter passed to a subsciber is the current playstate of the animation.
|
|||
/// </remarks>
|
|||
internal static IObservable<TimeSpan> AnimationsTimer |
|||
{ |
|||
get; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Diagnostics.Models |
|||
{ |
|||
internal class EventChainLink |
|||
{ |
|||
public EventChainLink(object handler, bool handled, RoutingStrategies route) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(handler != null); |
|||
|
|||
this.Handler = handler; |
|||
this.Handled = handled; |
|||
this.Route = route; |
|||
} |
|||
|
|||
public object Handler { get; } |
|||
|
|||
public string HandlerName |
|||
{ |
|||
get |
|||
{ |
|||
if (Handler is INamed named && !string.IsNullOrEmpty(named.Name)) |
|||
{ |
|||
return named.Name + " (" + Handler.GetType().Name + ")"; |
|||
} |
|||
return Handler.GetType().Name; |
|||
} |
|||
} |
|||
|
|||
public bool Handled { get; } |
|||
|
|||
public RoutingStrategies Route { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// 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; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class EventOwnerTreeNode : EventTreeNodeBase |
|||
{ |
|||
private static readonly RoutedEvent[] s_defaultEvents = new RoutedEvent[] |
|||
{ |
|||
Button.ClickEvent, |
|||
InputElement.KeyDownEvent, |
|||
InputElement.KeyUpEvent, |
|||
InputElement.TextInputEvent, |
|||
InputElement.PointerReleasedEvent, |
|||
InputElement.PointerPressedEvent, |
|||
}; |
|||
|
|||
public EventOwnerTreeNode(Type type, IEnumerable<RoutedEvent> events, EventsViewModel vm) |
|||
: base(null, type.Name) |
|||
{ |
|||
this.Children = new AvaloniaList<EventTreeNodeBase>(events.OrderBy(e => e.Name) |
|||
.Select(e => new EventTreeNode(this, e, vm) { IsEnabled = s_defaultEvents.Contains(e) })); |
|||
this.IsExpanded = true; |
|||
} |
|||
|
|||
public override bool? IsEnabled |
|||
{ |
|||
get => base.IsEnabled; |
|||
set |
|||
{ |
|||
if (base.IsEnabled != value) |
|||
{ |
|||
base.IsEnabled = value; |
|||
if (_updateChildren && value != null) |
|||
{ |
|||
foreach (var child in Children) |
|||
{ |
|||
try |
|||
{ |
|||
child._updateParent = false; |
|||
child.IsEnabled = value; |
|||
} |
|||
finally |
|||
{ |
|||
child._updateParent = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
using Avalonia.Diagnostics.Models; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Threading; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class EventTreeNode : EventTreeNodeBase |
|||
{ |
|||
private RoutedEvent _event; |
|||
private EventsViewModel _parentViewModel; |
|||
private bool _isRegistered; |
|||
private FiredEvent _currentEvent; |
|||
|
|||
public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsViewModel vm) |
|||
: base(parent, @event.Name) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(@event != null); |
|||
Contract.Requires<ArgumentNullException>(vm != null); |
|||
|
|||
this._event = @event; |
|||
this._parentViewModel = vm; |
|||
} |
|||
|
|||
public override bool? IsEnabled |
|||
{ |
|||
get => base.IsEnabled; |
|||
set |
|||
{ |
|||
if (base.IsEnabled != value) |
|||
{ |
|||
base.IsEnabled = value; |
|||
UpdateTracker(); |
|||
if (Parent != null && _updateParent) |
|||
{ |
|||
try |
|||
{ |
|||
Parent._updateChildren = false; |
|||
Parent.UpdateChecked(); |
|||
} |
|||
finally |
|||
{ |
|||
Parent._updateChildren = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void UpdateTracker() |
|||
{ |
|||
if (IsEnabled.GetValueOrDefault() && !_isRegistered) |
|||
{ |
|||
_event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); |
|||
_isRegistered = true; |
|||
} |
|||
} |
|||
|
|||
private void HandleEvent(object sender, RoutedEventArgs e) |
|||
{ |
|||
if (!_isRegistered || IsEnabled == false) |
|||
return; |
|||
if (sender is IVisual v && DevTools.BelongsToDevTool(v)) |
|||
return; |
|||
|
|||
var s = sender; |
|||
var handled = e.Handled; |
|||
var route = e.Route; |
|||
|
|||
Action handler = delegate |
|||
{ |
|||
if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e)) |
|||
{ |
|||
_currentEvent = new FiredEvent(e, new EventChainLink(s, handled, route)); |
|||
|
|||
_parentViewModel.RecordedEvents.Add(_currentEvent); |
|||
|
|||
while (_parentViewModel.RecordedEvents.Count > 100) |
|||
_parentViewModel.RecordedEvents.RemoveAt(0); |
|||
} |
|||
else |
|||
{ |
|||
_currentEvent.AddToChain(new EventChainLink(s, handled, route)); |
|||
} |
|||
}; |
|||
|
|||
if (!Dispatcher.UIThread.CheckAccess()) |
|||
Dispatcher.UIThread.Post(handler); |
|||
else |
|||
handler(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Collections; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal abstract class EventTreeNodeBase : ViewModelBase |
|||
{ |
|||
internal bool _updateChildren = true; |
|||
internal bool _updateParent = true; |
|||
private bool _isExpanded; |
|||
private bool? _isEnabled = false; |
|||
|
|||
public EventTreeNodeBase(EventTreeNodeBase parent, string text) |
|||
{ |
|||
this.Parent = parent; |
|||
this.Text = text; |
|||
} |
|||
|
|||
public IAvaloniaReadOnlyList<EventTreeNodeBase> Children |
|||
{ |
|||
get; |
|||
protected set; |
|||
} |
|||
|
|||
public bool IsExpanded |
|||
{ |
|||
get { return _isExpanded; } |
|||
set { RaiseAndSetIfChanged(ref _isExpanded, value); } |
|||
} |
|||
|
|||
public virtual bool? IsEnabled |
|||
{ |
|||
get { return _isEnabled; } |
|||
set { RaiseAndSetIfChanged(ref _isEnabled, value); } |
|||
} |
|||
|
|||
public EventTreeNodeBase Parent |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
public string Text |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
internal void UpdateChecked() |
|||
{ |
|||
IsEnabled = GetValue(); |
|||
|
|||
bool? GetValue() |
|||
{ |
|||
if (Children == null) |
|||
return false; |
|||
bool? value = false; |
|||
for (int i = 0; i < Children.Count; i++) |
|||
{ |
|||
if (i == 0) |
|||
{ |
|||
value = Children[i].IsEnabled; |
|||
continue; |
|||
} |
|||
|
|||
if (value != Children[i].IsEnabled) |
|||
{ |
|||
value = null; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// 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.ObjectModel; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Windows.Input; |
|||
|
|||
using Avalonia.Controls; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class EventsViewModel : ViewModelBase |
|||
{ |
|||
private readonly IControl _root; |
|||
private FiredEvent _selectedEvent; |
|||
|
|||
public EventsViewModel(IControl root) |
|||
{ |
|||
this._root = root; |
|||
this.Nodes = RoutedEventRegistry.Instance.GetAllRegistered() |
|||
.GroupBy(e => e.OwnerType) |
|||
.OrderBy(e => e.Key.Name) |
|||
.Select(g => new EventOwnerTreeNode(g.Key, g, this)) |
|||
.ToArray(); |
|||
} |
|||
|
|||
public EventTreeNodeBase[] Nodes { get; } |
|||
|
|||
public ObservableCollection<FiredEvent> RecordedEvents { get; } = new ObservableCollection<FiredEvent>(); |
|||
|
|||
public FiredEvent SelectedEvent |
|||
{ |
|||
get => _selectedEvent; |
|||
set => RaiseAndSetIfChanged(ref _selectedEvent, value); |
|||
} |
|||
|
|||
private void Clear() |
|||
{ |
|||
RecordedEvents.Clear(); |
|||
} |
|||
} |
|||
|
|||
internal class BoolToBrushConverter : IValueConverter |
|||
{ |
|||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
return (bool)value ? Brushes.LightGreen : Brushes.Transparent; |
|||
} |
|||
|
|||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.ObjectModel; |
|||
|
|||
using Avalonia.Diagnostics.Models; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class FiredEvent : ViewModelBase |
|||
{ |
|||
private RoutedEventArgs _eventArgs; |
|||
private EventChainLink _handledBy; |
|||
|
|||
public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(eventArgs != null); |
|||
Contract.Requires<ArgumentNullException>(originator != null); |
|||
|
|||
this._eventArgs = eventArgs; |
|||
this.Originator = originator; |
|||
AddToChain(originator); |
|||
} |
|||
|
|||
public bool IsPartOfSameEventChain(RoutedEventArgs e) |
|||
{ |
|||
return e == _eventArgs; |
|||
} |
|||
|
|||
public RoutedEvent Event => _eventArgs.RoutedEvent; |
|||
|
|||
public bool IsHandled => HandledBy?.Handled == true; |
|||
|
|||
public ObservableCollection<EventChainLink> EventChain { get; } = new ObservableCollection<EventChainLink>(); |
|||
|
|||
public string DisplayText |
|||
{ |
|||
get |
|||
{ |
|||
if (IsHandled) |
|||
{ |
|||
return $"{Event.Name} on {Originator.HandlerName};" + Environment.NewLine + |
|||
$"strategies: {Event.RoutingStrategies}; handled by: {HandledBy.HandlerName}"; |
|||
} |
|||
return $"{Event.Name} on {Originator.HandlerName}; strategies: {Event.RoutingStrategies}"; |
|||
} |
|||
} |
|||
|
|||
public EventChainLink Originator { get; } |
|||
|
|||
public EventChainLink HandledBy |
|||
{ |
|||
get { return _handledBy; } |
|||
set |
|||
{ |
|||
if (_handledBy != value) |
|||
{ |
|||
_handledBy = value; |
|||
RaisePropertyChanged(); |
|||
RaisePropertyChanged(nameof(IsHandled)); |
|||
RaisePropertyChanged(nameof(DisplayText)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void AddToChain(object handler, bool handled, RoutingStrategies route) |
|||
{ |
|||
AddToChain(new EventChainLink(handler, handled, route)); |
|||
} |
|||
|
|||
public void AddToChain(EventChainLink link) |
|||
{ |
|||
EventChain.Add(link); |
|||
if (HandledBy == null && link.Handled) |
|||
HandledBy = link; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"> |
|||
<UserControl.Resources> |
|||
<vm:BoolToBrushConverter x:Key="boolToBrush" /> |
|||
</UserControl.Resources> |
|||
<Grid ColumnDefinitions="*,4,3*"> |
|||
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" Grid.RowSpan="2"> |
|||
<TreeView.DataTemplates> |
|||
<TreeDataTemplate DataType="vm:EventTreeNodeBase" |
|||
ItemsSource="{Binding Children}"> |
|||
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsEnabled, Mode=TwoWay}" /> |
|||
</TreeDataTemplate> |
|||
</TreeView.DataTemplates> |
|||
<TreeView.Styles> |
|||
<Style Selector="TreeViewItem"> |
|||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> |
|||
</Style> |
|||
</TreeView.Styles> |
|||
</TreeView> |
|||
|
|||
<GridSplitter Width="4" Grid.Column="1" /> |
|||
<Grid RowDefinitions="*,4,2*,Auto" Grid.Column="2"> |
|||
<ListBox Name="eventsList" Items="{Binding RecordedEvents}" SelectedItem="{Binding SelectedEvent, Mode=TwoWay}"> |
|||
<ListBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<TextBlock Background="{Binding IsHandled, Converter={StaticResource boolToBrush}}" Text="{Binding DisplayText}" /> |
|||
</DataTemplate> |
|||
</ListBox.ItemTemplate> |
|||
</ListBox> |
|||
<GridSplitter Height="4" Grid.Row="1" /> |
|||
<DockPanel Grid.Row="2" LastChildFill="True"> |
|||
<TextBlock DockPanel.Dock="Top" FontSize="16" Text="Event chain:" /> |
|||
<ListBox Items="{Binding SelectedEvent.EventChain}"> |
|||
<ListBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<StackPanel Orientation="Horizontal" Background="{Binding Handled, Converter={StaticResource boolToBrush}}"> |
|||
<TextBlock Text="{Binding Route}" /> |
|||
<TextBlock Text=": " /> |
|||
<TextBlock Text="{Binding HandlerName}" /> |
|||
<TextBlock Text=" handled: " /> |
|||
<TextBlock Text="{Binding Handled}" /> |
|||
</StackPanel> |
|||
</DataTemplate> |
|||
</ListBox.ItemTemplate> |
|||
</ListBox> |
|||
</DockPanel> |
|||
<StackPanel Orientation="Horizontal" Grid.Row="3"> |
|||
<Button Content="Clear" Margin="3" Command="{Binding Clear}" /> |
|||
</StackPanel> |
|||
</Grid> |
|||
</Grid> |
|||
</UserControl> |
|||
@ -0,0 +1,32 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Linq; |
|||
|
|||
using Avalonia.Controls; |
|||
using Avalonia.Diagnostics.ViewModels; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace Avalonia.Diagnostics.Views |
|||
{ |
|||
public class EventsView : UserControl |
|||
{ |
|||
private ListBox _events; |
|||
|
|||
public EventsView() |
|||
{ |
|||
this.InitializeComponent(); |
|||
_events = this.FindControl<ListBox>("events"); |
|||
} |
|||
|
|||
private void RecordedEvents_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
_events.ScrollIntoView(_events.Items.OfType<FiredEvent>().LastOrDefault()); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Rendering; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public class RenderLoopClock : ClockBase, IRenderLoopTask, IGlobalClock |
|||
{ |
|||
protected override void Stop() |
|||
{ |
|||
AvaloniaLocator.Current.GetService<IRenderLoop>().Remove(this); |
|||
} |
|||
|
|||
bool IRenderLoopTask.NeedsUpdate => HasSubscriptions; |
|||
|
|||
void IRenderLoopTask.Render() |
|||
{ |
|||
} |
|||
|
|||
void IRenderLoopTask.Update(TimeSpan time) |
|||
{ |
|||
Pulse(time); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Media.Immutable; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// A collection of <see cref="GradientStop"/>s.
|
|||
/// </summary>
|
|||
public class GradientStops : AvaloniaList<GradientStop> |
|||
{ |
|||
public GradientStops() |
|||
{ |
|||
ResetBehavior = ResetBehavior.Remove; |
|||
} |
|||
|
|||
public IReadOnlyList<ImmutableGradientStop> ToImmutable() |
|||
{ |
|||
return this.Select(x => new ImmutableGradientStop(x.Offset, x.Color)).ToList(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Signals to a self-rendering control that changes to the resource should invoke
|
|||
/// <see cref="Visual.InvalidateVisual"/>.
|
|||
/// </summary>
|
|||
public interface IAffectsRender |
|||
{ |
|||
/// <summary>
|
|||
/// Raised when the resource changes visually.
|
|||
/// </summary>
|
|||
event EventHandler Invalidated; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Describes the location and color of a transition point in a gradient.
|
|||
/// </summary>
|
|||
public interface IGradientStop |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the gradient stop color.
|
|||
/// </summary>
|
|||
Color Color { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the gradient stop offset.
|
|||
/// </summary>
|
|||
double Offset { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
namespace Avalonia.Media.Immutable |
|||
{ |
|||
/// <summary>
|
|||
/// Describes the location and color of a transition point in a gradient.
|
|||
/// </summary>
|
|||
public class ImmutableGradientStop : IGradientStop |
|||
{ |
|||
public ImmutableGradientStop(double offset, Color color) |
|||
{ |
|||
Offset = offset; |
|||
Color = color; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public double Offset { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public Color Color { get; } |
|||
} |
|||
} |
|||
@ -1,19 +1,28 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Rendering |
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the interface implemented by an application render loop.
|
|||
/// The application render loop.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The render loop is responsible for advancing the animation timer and updating the scene
|
|||
/// graph for visible windows.
|
|||
/// </remarks>
|
|||
public interface IRenderLoop |
|||
{ |
|||
/// <summary>
|
|||
/// Raised when the render loop ticks to signal a new frame should be drawn.
|
|||
/// Adds an update task.
|
|||
/// </summary>
|
|||
/// <param name="i">The update task.</param>
|
|||
/// <remarks>
|
|||
/// This event can be raised on any thread; it is the responsibility of the subscriber to
|
|||
/// switch execution to the right thread.
|
|||
/// Registered update tasks will be polled on each tick of the render loop after the
|
|||
/// animation timer has been pulsed.
|
|||
/// </remarks>
|
|||
event EventHandler<EventArgs> Tick; |
|||
void Add(IRenderLoopTask i); |
|||
|
|||
/// <summary>
|
|||
/// Removes an update task.
|
|||
/// </summary>
|
|||
/// <param name="i">The update task.</param>
|
|||
void Remove(IRenderLoopTask i); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
public interface IRenderLoopTask |
|||
{ |
|||
bool NeedsUpdate { get; } |
|||
void Update(TimeSpan time); |
|||
void Render(); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the interface implemented by an application render timer.
|
|||
/// </summary>
|
|||
public interface IRenderTimer |
|||
{ |
|||
/// <summary>
|
|||
/// Raised when the render timer ticks to signal a new frame should be drawn.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This event can be raised on any thread; it is the responsibility of the subscriber to
|
|||
/// switch execution to the right thread.
|
|||
/// </remarks>
|
|||
event Action<TimeSpan> Tick; |
|||
} |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using Avalonia.Logging; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// The application render loop.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The render loop is responsible for advancing the animation timer and updating the scene
|
|||
/// graph for visible windows.
|
|||
/// </remarks>
|
|||
public class RenderLoop : IRenderLoop |
|||
{ |
|||
private readonly IDispatcher _dispatcher; |
|||
private List<IRenderLoopTask> _items = new List<IRenderLoopTask>(); |
|||
private IRenderTimer _timer; |
|||
private int inTick; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RenderLoop"/> class.
|
|||
/// </summary>
|
|||
public RenderLoop() |
|||
{ |
|||
_dispatcher = Dispatcher.UIThread; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RenderLoop"/> class.
|
|||
/// </summary>
|
|||
/// <param name="timer">The render timer.</param>
|
|||
/// <param name="dispatcher">The UI thread dispatcher.</param>
|
|||
public RenderLoop(IRenderTimer timer, IDispatcher dispatcher) |
|||
{ |
|||
_timer = timer; |
|||
_dispatcher = dispatcher; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the render timer.
|
|||
/// </summary>
|
|||
protected IRenderTimer Timer |
|||
{ |
|||
get |
|||
{ |
|||
if (_timer == null) |
|||
{ |
|||
_timer = AvaloniaLocator.Current.GetService<IRenderTimer>(); |
|||
} |
|||
|
|||
return _timer; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Add(IRenderLoopTask i) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(i != null); |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
|
|||
_items.Add(i); |
|||
|
|||
if (_items.Count == 1) |
|||
{ |
|||
Timer.Tick += TimerTick; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Remove(IRenderLoopTask i) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(i != null); |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
|
|||
_items.Remove(i); |
|||
|
|||
if (_items.Count == 0) |
|||
{ |
|||
Timer.Tick -= TimerTick; |
|||
} |
|||
} |
|||
|
|||
private async void TimerTick(TimeSpan time) |
|||
{ |
|||
if (Interlocked.CompareExchange(ref inTick, 1, 0) == 0) |
|||
{ |
|||
try |
|||
{ |
|||
if (_items.Any(item => item.NeedsUpdate)) |
|||
{ |
|||
await _dispatcher.InvokeAsync(() => |
|||
{ |
|||
foreach (var i in _items) |
|||
{ |
|||
i.Update(time); |
|||
} |
|||
}, DispatcherPriority.Render).ConfigureAwait(false); |
|||
} |
|||
|
|||
foreach (var i in _items) |
|||
{ |
|||
i.Render(); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.Error(LogArea.Visual, this, "Exception in render loop: {Error}", ex); |
|||
} |
|||
finally |
|||
{ |
|||
Interlocked.Exchange(ref inTick, 0); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,31 +0,0 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using MonoMac.Foundation; |
|||
|
|||
namespace Avalonia.MonoMac |
|||
{ |
|||
//TODO: Switch to using CVDisplayLink
|
|||
public class RenderLoop : IRenderLoop |
|||
{ |
|||
private readonly object _lock = new object(); |
|||
private readonly IDisposable _timer; |
|||
|
|||
public RenderLoop() |
|||
{ |
|||
_timer = AvaloniaLocator.Current.GetService<IRuntimePlatform>().StartSystemTimer(new TimeSpan(0, 0, 0, 0, 1000 / 60), |
|||
() => |
|||
{ |
|||
lock (_lock) |
|||
{ |
|||
using (new NSAutoreleasePool()) |
|||
{ |
|||
Tick?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public event EventHandler<EventArgs> Tick; |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using MonoMac.Foundation; |
|||
|
|||
namespace Avalonia.MonoMac |
|||
{ |
|||
//TODO: Switch to using CVDisplayLink
|
|||
public class RenderTimer : DefaultRenderTimer |
|||
{ |
|||
public RenderTimer(int framesPerSecond) : base(framesPerSecond) |
|||
{ |
|||
} |
|||
|
|||
protected override IDisposable StartCore(Action<TimeSpan> tick) |
|||
{ |
|||
return AvaloniaLocator.Current.GetService<IRuntimePlatform>().StartSystemTimer( |
|||
TimeSpan.FromSeconds(1.0 / FramesPerSecond), |
|||
() => |
|||
{ |
|||
using (new NSAutoreleasePool()) |
|||
{ |
|||
tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount)); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue