Skip to content

Commit

Permalink
Merge pull request #334 from AvaloniaCommunity/removeSystemReactive
Browse files Browse the repository at this point in the history
Remove System.Reactive dependency
  • Loading branch information
SKProCH committed Jan 8, 2024
2 parents 9f93ece + 16192e5 commit 83512e4
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 107 deletions.
1 change: 1 addition & 0 deletions Material.Styles/Assists/ShadowAssist.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Material.Styles.Internal;

namespace Material.Styles.Assists {
public static class ShadowProvider {
Expand Down
16 changes: 6 additions & 10 deletions Material.Styles/Assists/TransitionAssist.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
using System;
using Avalonia;
using Avalonia;
using Avalonia.Data;
using Material.Styles.Internal;

namespace Material.Styles.Assists
{
public static class TransitionAssist
{
namespace Material.Styles.Assists {
public static class TransitionAssist {
/// <summary>
/// Allows transitions to be disabled where supported. Note this is an inheritable property.
/// </summary>
public static readonly AvaloniaProperty<bool> DisableTransitionsProperty =
AvaloniaProperty.RegisterAttached<AvaloniaObject, bool>(
"DisableTransitions", typeof(TransitionAssist), false, true, BindingMode.TwoWay);

static TransitionAssist()
{
DisableTransitionsProperty.Changed.Subscribe(args =>
{
static TransitionAssist() {
DisableTransitionsProperty.Changed.Subscribe(args => {
if (args.Sender is not StyledElement styledElement) return;
styledElement.Classes.Set("no-transitions", args.NewValue.Value);
Expand Down
53 changes: 27 additions & 26 deletions Material.Styles/Controls/CircleClockPicker.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
Expand Down Expand Up @@ -32,6 +31,16 @@ public class CircleClockPicker : TemplatedControl {

public static readonly StyledProperty<int> CellShiftNumberProperty =
AvaloniaProperty.Register<CircleClockPicker, int>(nameof(CellShiftNumber));
private readonly Dictionary<int, CircleClockPickerCell> _cachedAccessors = new();
private Panel? _cellPanel;

private bool _isDragging;
private Control? _pointer;
private Control? _pointerPin;

private int? _value;

static CircleClockPicker() { }

public int? Value {
get => _value;
Expand Down Expand Up @@ -73,14 +82,9 @@ public int CellShiftNumber {

public event EventHandler? AfterDrag;

static CircleClockPicker() { }

protected override void OnApplyTemplate(TemplateAppliedEventArgs e) {
base.OnApplyTemplate(e);

_subscription?.Dispose();
_subscription = null;

var pointer = e.NameScope.Find<Control>("PART_Pointer");
var canvas = e.NameScope.Find<Canvas>("PART_CellPanel");
var pointerPin = e.NameScope.Find<Control>("PART_PointerPin");
Expand All @@ -89,21 +93,27 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) {
_pointerPin = pointerPin;
_cellPanel = canvas;

_subscription = new CompositeDisposable {
MinimumProperty.Changed.Subscribe(OnNext),
MaximumProperty.Changed.Subscribe(OnNext),
StepFrequencyProperty.Changed.Subscribe(OnNext),
FirstLabelOverrideProperty.Changed.Subscribe(OnNext),
RadiusMultiplierProperty.Changed.Subscribe(OnNext),
BoundsProperty.Changed.Subscribe(OnCanvasResize)
};

UpdateCellPanel();
AdjustPointer();
UpdateVisual(_value);
}

private void OnCanvasResize(AvaloniaPropertyChangedEventArgs<Rect> obj) {
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) {
base.OnPropertyChanged(change);
if (change.Property == MinimumProperty ||
change.Property == MaximumProperty ||
change.Property == StepFrequencyProperty ||
change.Property == FirstLabelOverrideProperty ||
change.Property == RadiusMultiplierProperty) {
OnNext(change);
return;
}

if (change.Property == BoundsProperty) OnCanvasResize(change);
}

private void OnCanvasResize(AvaloniaPropertyChangedEventArgs obj) {
if (!ReferenceEquals(obj.Sender, _cellPanel))
return;

Expand Down Expand Up @@ -140,15 +150,6 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) {
AfterDrag?.Invoke(this, EventArgs.Empty);
}

private bool _isDragging;
private Control? _pointer;
private Control? _pointerPin;
private Panel? _cellPanel;
private readonly Dictionary<int, CircleClockPickerCell> _cachedAccessors = new();
private IDisposable? _subscription;

private int? _value;

private void ProcessPointerEvent(Point point) {
var halfSize = (float)(Bounds.Width / 2);
var rad = (float)Math.Atan2(point.Y - halfSize, point.X - halfSize);
Expand Down Expand Up @@ -271,4 +272,4 @@ private void AdjustPointer() {
var radius = _cellPanel.Bounds.Width / 2;
_pointerPin.Height = radius * RadiusMultiplier;
}
}
}
13 changes: 8 additions & 5 deletions Material.Styles/Controls/MaterialInternalIcon.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ public class MaterialInternalIcon : TemplatedControl {

private Geometry? _data;

static MaterialInternalIcon() {
KindProperty.Changed.Subscribe(args => (args.Sender as MaterialInternalIcon)?.UpdateData());
}

/// <summary>
/// Gets or sets the icon to display.
/// </summary>
Expand Down Expand Up @@ -48,6 +44,13 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) {
UpdateData();
}

/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) {
base.OnPropertyChanged(change);

if (change.Property == KindProperty) UpdateData();
}

private void UpdateData() {
if (Kind is null)
return;
Expand All @@ -59,4 +62,4 @@ private void UpdateData() {
Data = null;
}
}
}
}
26 changes: 26 additions & 0 deletions Material.Styles/Internal/Disposable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;

namespace Material.Styles.Internal;

/// <summary>
/// Provides a set of static methods for creating <see cref="IDisposable"/> objects.
/// </summary>
internal static class Disposable {
/// <summary>
/// Gets the disposable that does nothing when disposed.
/// </summary>
public static IDisposable Empty => EmptyDisposable.Instance;

/// <summary>
/// Represents a disposable that does nothing on disposal.
/// </summary>
private sealed class EmptyDisposable : IDisposable {
public static readonly EmptyDisposable Instance = new();

private EmptyDisposable() { }

public void Dispose() {
// no op
}
}
}
146 changes: 146 additions & 0 deletions Material.Styles/Internal/LightweightObservableBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Threading;

namespace Material.Styles.Internal;

/// <summary>
/// Lightweight base class for observable implementations.
/// </summary>
/// <typeparam name="T">The observable type.</typeparam>
/// <remarks>
/// ObservableBase{T} is rather heavyweight in terms of allocations and memory
/// usage. This class provides a more lightweight base for some internal observable types
/// in the Avalonia framework.
/// </remarks>
internal abstract class LightweightObservableBase<T> : IObservable<T> {
private Exception? _error;
private List<IObserver<T>>? _observers = new();

public bool HasObservers => _observers?.Count > 0;

public IDisposable Subscribe(IObserver<T> observer) {
_ = observer ?? throw new ArgumentNullException(nameof(observer));

//Dispatcher.UIThread.VerifyAccess();

var first = false;

for (;;) {
if (Volatile.Read(ref _observers) == null) {
if (_error != null)
observer.OnError(_error);
else
observer.OnCompleted();

return Disposable.Empty;
}

lock (this) {
if (_observers == null) continue;

first = _observers.Count == 0;
_observers.Add(observer);
break;
}
}

if (first) Initialize();

Subscribed(observer, first);

return new RemoveObserver(this, observer);
}

private void Remove(IObserver<T> observer) {
if (Volatile.Read(ref _observers) != null) {
lock (this) {
var observers = _observers;

if (observers != null) {
observers.Remove(observer);

if (observers.Count == 0) {
observers.TrimExcess();
Deinitialize();
}
}
}
}
}

protected abstract void Initialize();
protected abstract void Deinitialize();

protected void PublishNext(T value) {
if (Volatile.Read(ref _observers) != null) {
IObserver<T>[]? observers = null;
IObserver<T>? singleObserver = null;
lock (this) {
if (_observers == null) return;
if (_observers.Count == 1)
singleObserver = _observers[0];
else
observers = _observers.ToArray();
}
if (singleObserver != null)
singleObserver.OnNext(value);
else {
foreach (var observer in observers!) observer.OnNext(value);
}
}
}

protected void PublishCompleted() {
if (Volatile.Read(ref _observers) != null) {
IObserver<T>[] observers;

lock (this) {
if (_observers == null) return;
observers = _observers.ToArray();
Volatile.Write(ref _observers, null);
}

foreach (var observer in observers) observer.OnCompleted();

Deinitialize();
}
}

protected void PublishError(Exception error) {
if (Volatile.Read(ref _observers) != null) {

IObserver<T>[] observers;

lock (this) {
if (_observers == null) return;

_error = error;
observers = _observers.ToArray();
Volatile.Write(ref _observers, null);
}

foreach (var observer in observers) observer.OnError(error);

Deinitialize();
}
}

protected virtual void Subscribed(IObserver<T> observer, bool first) { }

private sealed class RemoveObserver : IDisposable {
private IObserver<T>? _observer;
private LightweightObservableBase<T>? _parent;

public RemoveObserver(LightweightObservableBase<T> parent, IObserver<T> observer) {
_parent = parent;
Volatile.Write(ref _observer, observer);
}

public void Dispose() {
var observer = _observer;
Interlocked.Exchange(ref _parent, null)?.Remove(observer!);
_observer = null;
}
}
}
21 changes: 21 additions & 0 deletions Material.Styles/Internal/LightweightSubject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace Material.Styles.Internal;

internal class LightweightSubject<T> : LightweightObservableBase<T> {
public void OnCompleted() {
PublishCompleted();
}

public void OnError(Exception error) {
PublishError(error);
}

public void OnNext(T value) {
PublishNext(value);
}

protected override void Initialize() { }

protected override void Deinitialize() { }
}
10 changes: 10 additions & 0 deletions Material.Styles/Internal/Observable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using Avalonia.Reactive;

namespace Material.Styles.Internal;

internal static class Observable {
public static IDisposable Subscribe<T>(this IObservable<T> source, Action<T> action) {
return source.Subscribe(new AnonymousObserver<T>(action));
}
}
1 change: 0 additions & 1 deletion Material.Styles/Material.Styles.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,5 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Reactive" />
</ItemGroup>
</Project>
Loading

0 comments on commit 83512e4

Please sign in to comment.