Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove System.Reactive dependency #334

Merged
merged 7 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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