initial work on components required for the music box.
ADD
WalletWasabi.Fluent/Controls/BlurBehind.cs
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Skia;
+using JetBrains.Annotations;
+using SkiaSharp;
+
+namespace WalletWasabi.Fluent.Controls;
+
+public class CustomBlurBehind : Control
+{
+ public static readonly StyledProperty<ExperimentalAcrylicMaterial> MaterialProperty =
+ AvaloniaProperty.Register<CustomBlurBehind, ExperimentalAcrylicMaterial>(
+ "Material");
+
+ public ExperimentalAcrylicMaterial Material
+ {
+ get => GetValue(MaterialProperty);
+ set => SetValue(MaterialProperty, value);
+ }
+
+ static ImmutableExperimentalAcrylicMaterial DefaultAcrylicMaterial =
+ (ImmutableExperimentalAcrylicMaterial)new ExperimentalAcrylicMaterial()
+ {
+ MaterialOpacity = 0.0,
+ TintColor = Colors.Azure,
+ TintOpacity = 0.0,
+ PlatformTransparencyCompensationLevel = 0
+ }.ToImmutable();
+
+ static CustomBlurBehind()
+ {
+ AffectsRender<CustomBlurBehind>(MaterialProperty);
+ }
+
+ [CanBeNull] private static SKShader s_acrylicNoiseShader;
+
+ class BlurBehindRenderOperation : ICustomDrawOperation
+ {
+ private readonly ImmutableExperimentalAcrylicMaterial _material;
+ private readonly Rect _bounds;
+
+ public BlurBehindRenderOperation(ImmutableExperimentalAcrylicMaterial material, Rect bounds)
+ {
+ _material = material;
+ _bounds = bounds;
+ }
+
+ public void Dispose()
+ {
+
+ }
+
+ public bool HitTest(Point p) => _bounds.Contains(p);
+
+
+ static SKColorFilter CreateAlphaColorFilter(double opacity)
+ {
+ if (opacity > 1)
+ {
+ opacity = 1;
+ }
+
+ var c = new byte[256];
+ var a = new byte[256];
+ for (var i = 0; i < 256; i++)
+ {
+ c[i] = (byte)i;
+ a[i] = (byte)(i * opacity);
+ }
+
+ return SKColorFilter.CreateTable(a, c, c, c);
+ }
+
+ public void Render(IDrawingContextImpl context)
+ {
+ if (context is not ISkiaDrawingContextImpl skia)
+ {
+ return;
+ }
+
+ if (!skia.SkCanvas.TotalMatrix.TryInvert(out var currentInvertedTransform))
+ {
+ return;
+ }
+
+
+ using var backgroundSnapshot = skia.SkSurface.Snapshot();
+ using var backdropShader = SKShader.CreateImage(backgroundSnapshot, SKShaderTileMode.Clamp,
+ SKShaderTileMode.Clamp, currentInvertedTransform);
+
+ using var blurred = SKSurface.Create(skia.GrContext, false, new SKImageInfo(
+ (int)Math.Ceiling(_bounds.Width),
+ (int)Math.Ceiling(_bounds.Height), SKImageInfo.PlatformColorType, SKAlphaType.Premul));
+ using (var filter = SKImageFilter.CreateBlur(10, 10, SKShaderTileMode.Clamp))
+ using (var blurPaint = new SKPaint
+ {
+ Shader = backdropShader,
+ ImageFilter = filter
+ })
+ {
+ blurred.Canvas.DrawRect(0, 0, (float)_bounds.Width, (float)_bounds.Height, blurPaint);
+ }
+
+ using (var blurSnap = blurred.Snapshot())
+ using (var blurSnapShader = SKShader.CreateImage(blurSnap))
+ using (var blurSnapPaint = new SKPaint
+ {
+ Shader = blurSnapShader,
+ IsAntialias = true
+ })
+ {
+ skia.SkCanvas.DrawRect(0, 0, (float)_bounds.Width, (float)_bounds.Height, blurSnapPaint);
+ }
+ }
+
+ public Rect Bounds => _bounds.Inflate(4);
+
+ public bool Equals(ICustomDrawOperation? other)
+ {
+ return other is BlurBehindRenderOperation op && op._bounds == _bounds && op._material.Equals(_material);
+ }
+ }
+
+
+ public override void Render(DrawingContext context)
+ {
+ var mat = Material != null
+ ? (ImmutableExperimentalAcrylicMaterial)Material.ToImmutable()
+ : DefaultAcrylicMaterial;
+ context.Custom(new BlurBehindRenderOperation(mat, new Rect(default, Bounds.Size)));
+ }
+}
\ No newline at end of file

ADD
WalletWasabi.Fluent/Controls/SpectrumControl.cs
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Media;
+using Avalonia.Threading;
+
+namespace WalletWasabi.Fluent.Controls;
+
+public class SpectrumControl : TemplatedControl
+{
+ private readonly Random _random;
+ private Pen _linePen;
+ private ISolidColorBrush _lineBrush;
+ private IConicGradientBrush _gradientBrush;
+ private DispatcherTimer _timer;
+ private double[] _bins;
+ private double[] _averaged;
+ private const int NumBins = 250;
+ private const int NumAverages = 30;
+
+ public SpectrumControl()
+ {
+ _bins = new double[NumBins];
+ _averaged = new double[NumBins];
+
+ _random = new Random(DateTime.Now.Millisecond);
+ _lineBrush = SolidColorBrush.Parse("#97D234");
+ Background = new RadialGradientBrush()
+ {
+ GradientStops =
+ {
+ new GradientStop { Color = Color.Parse("#00000D21"), Offset = 0 },
+ new GradientStop { Color = Color.Parse("#FF000D21"), Offset = 1 }
+ }
+ };
+
+ DispatcherTimer.Run(() =>
+ {
+ for (int i = 0; i < NumBins; i++)
+ {
+ _bins[i] = _random.NextDouble();
+ }
+ return true;
+ }, TimeSpan.FromSeconds(0.2));
+
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ _linePen = new Pen(_lineBrush, finalSize.Width / NumBins);
+
+ return base.ArrangeOverride(finalSize);
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ base.Render(context);
+
+ var thickness = Bounds.Width / NumBins;
+ var center = (Bounds.Width / 2);
+
+ double x = 0;
+
+ for (int i = 0; i < NumBins; i++)
+ {
+ _averaged[i] -= _averaged[i] / NumAverages;
+ _averaged[i] += _bins[i] / NumAverages;
+
+ var dCenter = Math.Abs(x - center);
+ var multiplier = 1 - (dCenter / center);
+
+ context.DrawLine(_linePen, new Point(x, Bounds.Height),
+ new Point(x, Bounds.Height - multiplier * _averaged[i] * (Bounds.Height * 0.95)));
+ x += thickness;
+ }
+
+ context.FillRectangle(Background, Bounds);
+
+ Dispatcher.UIThread.Post(() => InvalidateVisual());
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/Views/MainView.axaml
WalletWasabi.Fluent/Views/MainView.axaml
<Panel>
<DockPanel>
<Panel Background="{DynamicResource RegionColor}" DockPanel.Dock="Bottom" VerticalAlignment="Bottom"
- Height="32">
+ Height="64">
+ <c:SpectrumControl Opacity="0.8" />
+ <c:CustomBlurBehind />
<statusBar:StatusBar DataContext="{Binding StatusBar}" />
+ <Border Padding="20 10" CornerRadius="4" BorderThickness="1" Background="#19FFFFFF" BorderBrush="#3CFFFFFF" Height="48" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="40 0">
+ <StackPanel Orientation="Horizontal" Spacing="20">
+ <TextBlock Text="Round in progress: 2m 15s remaining" VerticalAlignment="Center" />
+ <Separator Classes="vertical" />
+ <PathIcon Data="{StaticResource pause_regular}" />
+ <PathIcon Data="{StaticResource stop_regular}" />
+ </StackPanel>
+ </Border>
</Panel>
<Panel>
<Panel.Styles>

MODIFY
WalletWasabi.Fluent/WalletWasabi.Fluent.csproj
WalletWasabi.Fluent/WalletWasabi.Fluent.csproj
<PackageReference Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.12.2" />
+ <PackageReference Include="Avalonia.Skia" Version="$(AvaloniaVersion)" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="System.Runtime" Version="4.3.1" />

MODIFY
WalletWasabi.Fluent/packages.lock.json
WalletWasabi.Fluent/packages.lock.json
"System.Reactive": "5.0.0"
}
},
+ "Avalonia.Skia": {
+ "type": "Direct",
+ "requested": "[0.10.12, )",
+ "resolved": "0.10.12",
+ "contentHash": "3TGo8RLHaLqmU3chlyAqLkpw6vImfDMC30T18abpeYf1PIsNckRB+UFp12GDil9t/J9YB17zn4H6N+2plF4gZA==",
+ "dependencies": {
+ "Avalonia": "0.10.12",
+ "HarfBuzzSharp": "2.8.2-preview.178",
+ "HarfBuzzSharp.NativeAssets.Linux": "2.8.2-preview.178",
+ "HarfBuzzSharp.NativeAssets.WebAssembly": "2.8.2-preview.178",
+ "SkiaSharp": "2.88.0-preview.178",
+ "SkiaSharp.NativeAssets.Linux": "2.88.0-preview.178",
+ "SkiaSharp.NativeAssets.WebAssembly": "2.88.0-preview.178"
+ }
+ },
"Avalonia.Xaml.Behaviors": {
"type": "Direct",
"requested": "[0.10.12.2, )",
"System.Reactive": "5.0.0"
}
},
+ "HarfBuzzSharp": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "OUir5qn95QRtlc8RWKfU/63xYwtuAbylL2oAj3eBWgAsVoWnFrEv+Oh1sj0xjW7mogFGaeGtY40lqAD1srWJcQ==",
+ "dependencies": {
+ "HarfBuzzSharp.NativeAssets.Win32": "2.8.2-preview.178",
+ "HarfBuzzSharp.NativeAssets.macOS": "2.8.2-preview.178",
+ "System.Memory": "4.5.3"
+ }
+ },
+ "HarfBuzzSharp.NativeAssets.Linux": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "4scihdELcRpCEubBsUMHUJn93Xvx6ASj596WfO9y8CEuFNW0LBMDL71HBCyq5zXsn8HyGjLtoBLW0PpXbVnpjQ==",
+ "dependencies": {
+ "HarfBuzzSharp": "2.8.2-preview.178"
+ }
+ },
+ "HarfBuzzSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "QtmAs62il4vFtt3fFOXhhPDl7TX+NGu4tFB5qmnqUn+EnSJW7mxqNk1n9I7+Z2ORym0nTP4dhcRNtOpOS7Oenw=="
+ },
+ "HarfBuzzSharp.NativeAssets.WebAssembly": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "+S8qtBAVTrt+E85jZXPxYthUgSUq7iB6UZ0v0WFsy9gWhZ/hVE3hZJpcgeywT9H/SRX3ZIX+qzpKJlOM+mUcNA=="
+ },
+ "HarfBuzzSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "EgeF5uCZcAriIHmWq3hKNxz/jBJLeP/PKU4yI87UNkJCt4hYignOMjY0irl/rGVZtTL/G05xxf7TB6sjisi8sQ=="
+ },
"JetBrains.Annotations": {
"type": "Transitive",
"resolved": "10.3.0",
"System.Runtime.Serialization.Primitives": "4.3.0"
}
},
+ "SkiaSharp": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "arzd/44ykiBPqGWUuQqNTuJ49rhsXOg4Zw1p2Mm3B/5PZzV1wcTH4V+J+4ra8RS0KbIoy4KWeNF+zHAifNsiRg==",
+ "dependencies": {
+ "SkiaSharp.NativeAssets.Win32": "2.88.0-preview.178",
+ "SkiaSharp.NativeAssets.macOS": "2.88.0-preview.178",
+ "System.Memory": "4.5.3"
+ }
+ },
+ "SkiaSharp.NativeAssets.Linux": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "nc9C8zGvL2G7p0lcTPhN4EOt2Mozv6KLJinMwjF97sYoI5cpkXCPZSRTcyf8k49gAZaOd+UMGaygCAz/8vaaWg==",
+ "dependencies": {
+ "SkiaSharp": "2.88.0-preview.178"
+ }
+ },
+ "SkiaSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "+Hs3ku4buimzBHuc8FoyjOcE6eU5r98zcG7WH/s+doYQ1bFIjk+dKfqthgZ2o0NRAv8D3esq9rWrZTj12q+m1w=="
+ },
+ "SkiaSharp.NativeAssets.WebAssembly": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "u4Ss81oOlx0dhu5fxl4vK5f2Hm7psHDUSAoQValNV/BmixsW4TkETE3dOnHNRWwI56++tRG9dK33HimZDUrUpw=="
+ },
+ "SkiaSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "9og9GCkdZc/NYrmbsRzohmIBRlS1oFegJiBMsoG93qYjhh2o6q5QBYxd61zw5Mgeytl3qj4YM+6BNIF4tcF+6w=="
+ },
"Splat": {
"type": "Transitive",
"resolved": "10.0.1",
"runtime.any.System.Runtime": "4.3.0"
}
},
+ "HarfBuzzSharp.NativeAssets.Linux": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "4scihdELcRpCEubBsUMHUJn93Xvx6ASj596WfO9y8CEuFNW0LBMDL71HBCyq5zXsn8HyGjLtoBLW0PpXbVnpjQ==",
+ "dependencies": {
+ "HarfBuzzSharp": "2.8.2-preview.178"
+ }
+ },
+ "HarfBuzzSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "QtmAs62il4vFtt3fFOXhhPDl7TX+NGu4tFB5qmnqUn+EnSJW7mxqNk1n9I7+Z2ORym0nTP4dhcRNtOpOS7Oenw=="
+ },
+ "HarfBuzzSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "EgeF5uCZcAriIHmWq3hKNxz/jBJLeP/PKU4yI87UNkJCt4hYignOMjY0irl/rGVZtTL/G05xxf7TB6sjisi8sQ=="
+ },
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "6.0.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
}
},
+ "SkiaSharp.NativeAssets.Linux": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "nc9C8zGvL2G7p0lcTPhN4EOt2Mozv6KLJinMwjF97sYoI5cpkXCPZSRTcyf8k49gAZaOd+UMGaygCAz/8vaaWg==",
+ "dependencies": {
+ "SkiaSharp": "2.88.0-preview.178"
+ }
+ },
+ "SkiaSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "+Hs3ku4buimzBHuc8FoyjOcE6eU5r98zcG7WH/s+doYQ1bFIjk+dKfqthgZ2o0NRAv8D3esq9rWrZTj12q+m1w=="
+ },
+ "SkiaSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "9og9GCkdZc/NYrmbsRzohmIBRlS1oFegJiBMsoG93qYjhh2o6q5QBYxd61zw5Mgeytl3qj4YM+6BNIF4tcF+6w=="
+ },
"System.Collections": {
"type": "Transitive",
"resolved": "4.0.11",
"runtime.any.System.Runtime": "4.3.0"
}
},
+ "HarfBuzzSharp.NativeAssets.Linux": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "4scihdELcRpCEubBsUMHUJn93Xvx6ASj596WfO9y8CEuFNW0LBMDL71HBCyq5zXsn8HyGjLtoBLW0PpXbVnpjQ==",
+ "dependencies": {
+ "HarfBuzzSharp": "2.8.2-preview.178"
+ }
+ },
+ "HarfBuzzSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "QtmAs62il4vFtt3fFOXhhPDl7TX+NGu4tFB5qmnqUn+EnSJW7mxqNk1n9I7+Z2ORym0nTP4dhcRNtOpOS7Oenw=="
+ },
+ "HarfBuzzSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "EgeF5uCZcAriIHmWq3hKNxz/jBJLeP/PKU4yI87UNkJCt4hYignOMjY0irl/rGVZtTL/G05xxf7TB6sjisi8sQ=="
+ },
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "6.0.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
}
},
+ "SkiaSharp.NativeAssets.Linux": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "nc9C8zGvL2G7p0lcTPhN4EOt2Mozv6KLJinMwjF97sYoI5cpkXCPZSRTcyf8k49gAZaOd+UMGaygCAz/8vaaWg==",
+ "dependencies": {
+ "SkiaSharp": "2.88.0-preview.178"
+ }
+ },
+ "SkiaSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "+Hs3ku4buimzBHuc8FoyjOcE6eU5r98zcG7WH/s+doYQ1bFIjk+dKfqthgZ2o0NRAv8D3esq9rWrZTj12q+m1w=="
+ },
+ "SkiaSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "9og9GCkdZc/NYrmbsRzohmIBRlS1oFegJiBMsoG93qYjhh2o6q5QBYxd61zw5Mgeytl3qj4YM+6BNIF4tcF+6w=="
+ },
"System.Collections": {
"type": "Transitive",
"resolved": "4.0.11",
"runtime.any.System.Runtime": "4.3.0"
}
},
+ "HarfBuzzSharp.NativeAssets.Linux": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "4scihdELcRpCEubBsUMHUJn93Xvx6ASj596WfO9y8CEuFNW0LBMDL71HBCyq5zXsn8HyGjLtoBLW0PpXbVnpjQ==",
+ "dependencies": {
+ "HarfBuzzSharp": "2.8.2-preview.178"
+ }
+ },
+ "HarfBuzzSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "QtmAs62il4vFtt3fFOXhhPDl7TX+NGu4tFB5qmnqUn+EnSJW7mxqNk1n9I7+Z2ORym0nTP4dhcRNtOpOS7Oenw=="
+ },
+ "HarfBuzzSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "2.8.2-preview.178",
+ "contentHash": "EgeF5uCZcAriIHmWq3hKNxz/jBJLeP/PKU4yI87UNkJCt4hYignOMjY0irl/rGVZtTL/G05xxf7TB6sjisi8sQ=="
+ },
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "6.0.0",
"resolved": "4.3.0",
"contentHash": "Q+IBgaPYicSQs2tBlmXqbS25c/JLIthWrgrpMwxKSOobW/OqIMVFruUGfuaz4QABVzV8iKdCAbN7APY7Tclbnw=="
},
+ "SkiaSharp.NativeAssets.Linux": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "nc9C8zGvL2G7p0lcTPhN4EOt2Mozv6KLJinMwjF97sYoI5cpkXCPZSRTcyf8k49gAZaOd+UMGaygCAz/8vaaWg==",
+ "dependencies": {
+ "SkiaSharp": "2.88.0-preview.178"
+ }
+ },
+ "SkiaSharp.NativeAssets.macOS": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "+Hs3ku4buimzBHuc8FoyjOcE6eU5r98zcG7WH/s+doYQ1bFIjk+dKfqthgZ2o0NRAv8D3esq9rWrZTj12q+m1w=="
+ },
+ "SkiaSharp.NativeAssets.Win32": {
+ "type": "Transitive",
+ "resolved": "2.88.0-preview.178",
+ "contentHash": "9og9GCkdZc/NYrmbsRzohmIBRlS1oFegJiBMsoG93qYjhh2o6q5QBYxd61zw5Mgeytl3qj4YM+6BNIF4tcF+6w=="
+ },
"System.Collections": {
"type": "Transitive",
"resolved": "4.0.11",

initial work on components required for the music box.