committed by
GitHub
27 changed files with 1072 additions and 8 deletions
@ -0,0 +1,139 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.AcrylicPage"> |
|||
<Border Padding="20" HorizontalAlignment="Center"> |
|||
<StackPanel Spacing="20"> |
|||
|
|||
<ExperimentalAcrylicBorder Width="660" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="White" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
<StackPanel Spacing="5" Margin="40 10"> |
|||
<StackPanel Orientation="Horizontal"> |
|||
<TextBlock Text="TintOpacity" Foreground="Black" /> |
|||
<Slider Name="TintOpacitySlider" Minimum="0" Maximum="1" Value="0.9" Width="400" /> |
|||
<TextBlock Text="{Binding #TintOpacitySlider.Value}" Foreground="Black" /> |
|||
</StackPanel> |
|||
<StackPanel Orientation="Horizontal"> |
|||
<TextBlock Text="MaterialOpacity" Foreground="Black" /> |
|||
<Slider Name="MaterialOpacitySlider" Minimum="0" Maximum="1" Value="0.8" Width="400" /> |
|||
<TextBlock Text="{Binding #MaterialOpacitySlider.Value}" Foreground="Black" /> |
|||
</StackPanel> |
|||
</StackPanel> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
|
|||
<StackPanel Orientation="Horizontal" Spacing="20"> |
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#FF0000" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#00FF00" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#000000" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
</StackPanel> |
|||
|
|||
|
|||
<StackPanel Orientation="Horizontal" Spacing="20"> |
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#0000FF" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#FFFF00" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#000000" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
</StackPanel> |
|||
|
|||
<StackPanel Orientation="Horizontal" Spacing="20"> |
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="White" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#3c3c3c" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="White" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
</StackPanel> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="660" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="Red" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
</StackPanel> |
|||
</Border> |
|||
</UserControl> |
|||
@ -0,0 +1,19 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class AcrylicPage : UserControl |
|||
{ |
|||
public AcrylicPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines compensation levels for the platform depending on the transparency level.
|
|||
/// It controls the base opacity level of the 'tracing paper' layer that compensates
|
|||
/// for low blur radius.
|
|||
/// </summary>
|
|||
public struct AcrylicPlatformCompensationLevels |
|||
{ |
|||
public AcrylicPlatformCompensationLevels(double transparent, double blurred, double acrylic) |
|||
{ |
|||
TransparentLevel = transparent; |
|||
BlurLevel = blurred; |
|||
AcrylicBlurLevel = acrylic; |
|||
} |
|||
|
|||
public double TransparentLevel { get; } |
|||
|
|||
public double BlurLevel { get; } |
|||
|
|||
public double AcrylicBlurLevel { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using Avalonia.Controls.Utils; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class ExperimentalAcrylicBorder : Decorator |
|||
{ |
|||
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty = |
|||
Border.CornerRadiusProperty.AddOwner<ExperimentalAcrylicBorder>(); |
|||
|
|||
public static readonly StyledProperty<ExperimentalAcrylicMaterial> MaterialProperty = |
|||
AvaloniaProperty.Register<ExperimentalAcrylicBorder, ExperimentalAcrylicMaterial>(nameof(Material)); |
|||
|
|||
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper(); |
|||
|
|||
private IDisposable _subscription; |
|||
|
|||
static ExperimentalAcrylicBorder() |
|||
{ |
|||
AffectsRender<ExperimentalAcrylicBorder>( |
|||
MaterialProperty, |
|||
CornerRadiusProperty); |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the radius of the border rounded corners.
|
|||
/// </summary>
|
|||
public CornerRadius CornerRadius |
|||
{ |
|||
get { return GetValue(CornerRadiusProperty); } |
|||
set { SetValue(CornerRadiusProperty, value); } |
|||
} |
|||
|
|||
public ExperimentalAcrylicMaterial Material |
|||
{ |
|||
get => GetValue(MaterialProperty); |
|||
set => SetValue(MaterialProperty, value); |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
|
|||
var tl = (e.Root as TopLevel); |
|||
|
|||
_subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty) |
|||
.Subscribe(x => |
|||
{ |
|||
switch (x) |
|||
{ |
|||
case WindowTransparencyLevel.Transparent: |
|||
case WindowTransparencyLevel.None: |
|||
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel; |
|||
break; |
|||
|
|||
case WindowTransparencyLevel.Blur: |
|||
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel; |
|||
break; |
|||
|
|||
case WindowTransparencyLevel.AcrylicBlur: |
|||
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel; |
|||
break; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnDetachedFromVisualTree(e); |
|||
|
|||
_subscription?.Dispose(); |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc) |
|||
{ |
|||
var cornerRadius = CornerRadius; |
|||
|
|||
idc.DrawRectangle( |
|||
Material, |
|||
new RoundedRect( |
|||
new Rect(Bounds.Size), |
|||
cornerRadius.TopLeft, cornerRadius.TopRight, |
|||
cornerRadius.BottomRight, cornerRadius.BottomLeft)); |
|||
} |
|||
else |
|||
{ |
|||
_borderRenderHelper.Render(context, Bounds.Size, new Thickness(), CornerRadius, new SolidColorBrush(Material.FallbackColor), null, default); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the control.
|
|||
/// </summary>
|
|||
/// <param name="availableSize">The available size.</param>
|
|||
/// <returns>The desired size of the control.</returns>
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
return LayoutHelper.MeasureChild(Child, availableSize, Padding); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Arranges the control's child.
|
|||
/// </summary>
|
|||
/// <param name="finalSize">The size allocated to the control.</param>
|
|||
/// <returns>The space taken.</returns>
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
return LayoutHelper.ArrangeChild(Child, finalSize, Padding); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Background Sources for Acrylic.
|
|||
/// </summary>
|
|||
public enum AcrylicBackgroundSource |
|||
{ |
|||
/// <summary>
|
|||
/// The acrylic has no background.
|
|||
/// </summary>
|
|||
None, |
|||
|
|||
/// <summary>
|
|||
/// Cuts through all render layers to reveal the window background.
|
|||
/// This means if your window is transparent or blurred it
|
|||
/// will be blended with the material.
|
|||
/// </summary>
|
|||
Digger |
|||
} |
|||
} |
|||
@ -0,0 +1,345 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
public class ExperimentalAcrylicMaterial : AvaloniaObject, IMutableExperimentalAcrylicMaterial |
|||
{ |
|||
private Color _effectiveTintColor; |
|||
private Color _effectiveLuminosityColor; |
|||
|
|||
static ExperimentalAcrylicMaterial() |
|||
{ |
|||
AffectsRender<ExperimentalAcrylicMaterial>( |
|||
TintColorProperty, |
|||
BackgroundSourceProperty, |
|||
TintOpacityProperty, |
|||
MaterialOpacityProperty, |
|||
PlatformTransparencyCompensationLevelProperty); |
|||
|
|||
TintColorProperty.Changed.AddClassHandler<ExperimentalAcrylicMaterial>((b, e) => |
|||
{ |
|||
b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); |
|||
b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); |
|||
}); |
|||
|
|||
TintOpacityProperty.Changed.AddClassHandler<ExperimentalAcrylicMaterial>((b, e) => |
|||
{ |
|||
b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); |
|||
b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); |
|||
}); |
|||
|
|||
MaterialOpacityProperty.Changed.AddClassHandler<ExperimentalAcrylicMaterial>((b, e) => |
|||
{ |
|||
b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); |
|||
b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); |
|||
}); |
|||
|
|||
PlatformTransparencyCompensationLevelProperty.Changed.AddClassHandler<ExperimentalAcrylicMaterial>((b, e) => |
|||
{ |
|||
b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); |
|||
b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="TintColor"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<Color> TintColorProperty = |
|||
AvaloniaProperty.Register<ExperimentalAcrylicMaterial, Color>(nameof(TintColor)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="BackgroundSource"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<AcrylicBackgroundSource> BackgroundSourceProperty = |
|||
AvaloniaProperty.Register<ExperimentalAcrylicMaterial, AcrylicBackgroundSource>(nameof(BackgroundSource)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="TintOpacity"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> TintOpacityProperty = |
|||
AvaloniaProperty.Register<ExperimentalAcrylicMaterial, double>(nameof(TintOpacity), 0.8); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MaterialOpacity"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> MaterialOpacityProperty = |
|||
AvaloniaProperty.Register<ExperimentalAcrylicMaterial, double>(nameof(MaterialOpacity), 0.5); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PlatformTransparencyCompensationLevel"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> PlatformTransparencyCompensationLevelProperty = |
|||
AvaloniaProperty.Register<ExperimentalAcrylicMaterial, double>(nameof(PlatformTransparencyCompensationLevel), 0.0); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="FallbackColor"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<Color> FallbackColorProperty = |
|||
AvaloniaProperty.Register<ExperimentalAcrylicMaterial, Color>(nameof(FallbackColor)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public event EventHandler Invalidated; |
|||
|
|||
/// <summary>
|
|||
/// Gets or Sets the BackgroundSource <seealso cref="AcrylicBackgroundSource"/>.
|
|||
/// </summary>
|
|||
public AcrylicBackgroundSource BackgroundSource |
|||
{ |
|||
get => GetValue(BackgroundSourceProperty); |
|||
set => SetValue(BackgroundSourceProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or Sets the TintColor.
|
|||
/// </summary>
|
|||
public Color TintColor |
|||
{ |
|||
get => GetValue(TintColorProperty); |
|||
set => SetValue(TintColorProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or Sets the Tint Opacity.
|
|||
/// </summary>
|
|||
public double TintOpacity |
|||
{ |
|||
get => GetValue(TintOpacityProperty); |
|||
set => SetValue(TintOpacityProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or Sets the Fallback Color.
|
|||
/// This is used on rendering plaforms that dont support acrylic.
|
|||
/// </summary>
|
|||
public Color FallbackColor |
|||
{ |
|||
get => GetValue(FallbackColorProperty); |
|||
set => SetValue(FallbackColorProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or Sets the MaterialOpacity.
|
|||
/// This makes the material more or less opaque.
|
|||
/// </summary>
|
|||
public double MaterialOpacity |
|||
{ |
|||
get => GetValue(MaterialOpacityProperty); |
|||
set => SetValue(MaterialOpacityProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or Sets the PlatformTransparencyCompensationLevel.
|
|||
/// This value defines the minimum <see cref="MaterialOpacity"/> that can be used.
|
|||
/// It means material opacity is re-scaled from this value to 1.
|
|||
/// </summary>
|
|||
public double PlatformTransparencyCompensationLevel |
|||
{ |
|||
get => GetValue(PlatformTransparencyCompensationLevelProperty); |
|||
set => SetValue(PlatformTransparencyCompensationLevelProperty, value); |
|||
} |
|||
|
|||
Color IExperimentalAcrylicMaterial.MaterialColor => _effectiveLuminosityColor; |
|||
|
|||
Color IExperimentalAcrylicMaterial.TintColor => _effectiveTintColor; |
|||
|
|||
private struct HsvColor |
|||
{ |
|||
public float Hue { get; set; } |
|||
public float Saturation { get; set; } |
|||
public float Value { get; set; } |
|||
} |
|||
|
|||
private static HsvColor RgbToHsv(Color color) |
|||
{ |
|||
var r = color.R / 255.0f; |
|||
var g = color.G / 255.0f; |
|||
var b = color.B / 255.0f; |
|||
var max = Math.Max(r, Math.Max(g, b)); |
|||
var min = Math.Min(r, Math.Min(g, b)); |
|||
|
|||
float h, s, v; |
|||
h = v = max; |
|||
|
|||
var d = max - min; |
|||
s = max == 0 ? 0 : d / max; |
|||
|
|||
if (max == min) |
|||
{ |
|||
h = 0; // achromatic
|
|||
} |
|||
else |
|||
{ |
|||
if (max == r) |
|||
{ |
|||
h = (g - b) / d + (g < b ? 6 : 0); |
|||
} |
|||
else if (max == g) |
|||
{ |
|||
h = (b - r) / d + 2; |
|||
} |
|||
else if (max == b) |
|||
{ |
|||
h = (r - g) / d + 4; |
|||
} |
|||
|
|||
h /= 6; |
|||
} |
|||
|
|||
return new HsvColor { Hue = h, Saturation = s, Value = v }; |
|||
} |
|||
|
|||
private static Color GetEffectiveTintColor(Color tintColor, double tintOpacity) |
|||
{ |
|||
// Update tintColor's alpha with the combined opacity value
|
|||
double tintOpacityModifier = GetTintOpacityModifier(tintColor); |
|||
|
|||
return new Color((byte)(255 * ((255.0 / tintColor.A) * tintOpacity) * tintOpacityModifier), tintColor.R, tintColor.G, tintColor.B); |
|||
} |
|||
|
|||
private static double GetTintOpacityModifier(Color tintColor) |
|||
{ |
|||
// This method supresses the maximum allowable tint opacity depending on the luminosity and saturation of a color by
|
|||
// compressing the range of allowable values - for example, a user-defined value of 100% will be mapped to 45% for pure
|
|||
// white (100% luminosity), 85% for pure black (0% luminosity), and 90% for pure gray (50% luminosity). The intensity of
|
|||
// the effect increases linearly as luminosity deviates from 50%. After this effect is calculated, we cancel it out
|
|||
// linearly as saturation increases from zero.
|
|||
|
|||
const double midPoint = 0.5; // Mid point of HsvV range that these calculations are based on. This is here for easy tuning.
|
|||
|
|||
const double whiteMaxOpacity = 0.2; // 100% luminosity
|
|||
const double midPointMaxOpacity = 0.45; // 50% luminosity
|
|||
const double blackMaxOpacity = 0.45; // 0% luminosity
|
|||
|
|||
var hsv = RgbToHsv(tintColor); |
|||
|
|||
double opacityModifier = midPointMaxOpacity; |
|||
|
|||
if (hsv.Value != midPoint) |
|||
{ |
|||
// Determine maximum suppression amount
|
|||
double lowestMaxOpacity = midPointMaxOpacity; |
|||
double maxDeviation = midPoint; |
|||
|
|||
if (hsv.Value > midPoint) |
|||
{ |
|||
lowestMaxOpacity = whiteMaxOpacity; // At white (100% hsvV)
|
|||
maxDeviation = 1 - maxDeviation; |
|||
} |
|||
else if (hsv.Value < midPoint) |
|||
{ |
|||
lowestMaxOpacity = blackMaxOpacity; // At black (0% hsvV)
|
|||
} |
|||
|
|||
double maxOpacitySuppression = midPointMaxOpacity - lowestMaxOpacity; |
|||
|
|||
// Determine normalized deviation from the midpoint
|
|||
double deviation = Math.Abs(hsv.Value - midPoint); |
|||
double normalizedDeviation = deviation / maxDeviation; |
|||
|
|||
// If we have saturation, reduce opacity suppression to allow that color to come through more
|
|||
if (hsv.Saturation > 0) |
|||
{ |
|||
// Dampen opacity suppression based on how much saturation there is
|
|||
maxOpacitySuppression *= Math.Max(1 - (hsv.Saturation * 2), 0.0); |
|||
} |
|||
|
|||
double opacitySuppression = maxOpacitySuppression * normalizedDeviation; |
|||
|
|||
opacityModifier = midPointMaxOpacity - opacitySuppression; |
|||
} |
|||
|
|||
return opacityModifier; |
|||
} |
|||
|
|||
private Color GetEffectiveLuminosityColor() |
|||
{ |
|||
double? luminosityOpacity = MaterialOpacity; |
|||
|
|||
return GetLuminosityColor(luminosityOpacity); |
|||
} |
|||
|
|||
private static byte Trim(double value) |
|||
{ |
|||
value = Math.Min(Math.Floor(value * 256), 255); |
|||
|
|||
if (value < 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
else if (value > 255) |
|||
{ |
|||
return 255; |
|||
} |
|||
|
|||
return (byte)value; |
|||
} |
|||
|
|||
private static float RGBMax(Color color) |
|||
{ |
|||
if (color.R > color.G) |
|||
return (color.R > color.B) ? color.R : color.B; |
|||
else |
|||
return (color.G > color.B) ? color.G : color.B; |
|||
} |
|||
|
|||
private static float RGBMin(Color color) |
|||
{ |
|||
if (color.R < color.G) |
|||
return (color.R < color.B) ? color.R : color.B; |
|||
else |
|||
return (color.G < color.B) ? color.G : color.B; |
|||
} |
|||
|
|||
// The tintColor passed into this method should be the original, unmodified color created using user values for TintColor + TintOpacity
|
|||
private Color GetLuminosityColor(double? luminosityOpacity) |
|||
{ |
|||
// Calculate the HSL lightness value of the color.
|
|||
var max = (float)RGBMax(TintColor) / 255.0f; |
|||
var min = (float)RGBMin(TintColor) / 255.0f; |
|||
|
|||
var lightness = (max + min) / 2.0; |
|||
|
|||
lightness = 1 - ((1 - lightness) * luminosityOpacity.Value); |
|||
|
|||
lightness = 0.13 + (lightness * 0.74); |
|||
|
|||
var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); |
|||
|
|||
var compensationMultiplier = 1 - PlatformTransparencyCompensationLevel; |
|||
return new Color((byte)(255 * Math.Max(Math.Min(PlatformTransparencyCompensationLevel + (luminosityOpacity.Value * compensationMultiplier), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Marks a property as affecting the brush's visual representation.
|
|||
/// </summary>
|
|||
/// <param name="properties">The properties.</param>
|
|||
/// <remarks>
|
|||
/// After a call to this method in a brush's static constructor, any change to the
|
|||
/// property will cause the <see cref="Invalidated"/> event to be raised on the brush.
|
|||
/// </remarks>
|
|||
protected static void AffectsRender<T>(params AvaloniaProperty[] properties) |
|||
where T : ExperimentalAcrylicMaterial |
|||
{ |
|||
static void Invalidate(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
(e.Sender as T)?.RaiseInvalidated(EventArgs.Empty); |
|||
} |
|||
|
|||
foreach (var property in properties) |
|||
{ |
|||
property.Changed.Subscribe(e => Invalidate(e)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Raises the <see cref="Invalidated"/> event.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); |
|||
|
|||
public IExperimentalAcrylicMaterial ToImmutable() |
|||
{ |
|||
return new ImmutableExperimentalAcrylicMaterial(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Experimental Interface for producing Acrylic-like materials.
|
|||
/// </summary>
|
|||
public interface IExperimentalAcrylicMaterial |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the <see cref="AcrylicBackgroundSource"/> of the material.
|
|||
/// </summary>
|
|||
AcrylicBackgroundSource BackgroundSource { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the TintColor of the material.
|
|||
/// </summary>
|
|||
Color TintColor { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the TintOpacity of the material.
|
|||
/// </summary>
|
|||
double TintOpacity { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective material color.
|
|||
/// </summary>
|
|||
Color MaterialColor { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the fallback color.
|
|||
/// </summary>
|
|||
Color FallbackColor { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a mutable brush which can return an immutable clone of itself.
|
|||
/// </summary>
|
|||
public interface IMutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IAffectsRender |
|||
{ |
|||
/// <summary>
|
|||
/// Creates an immutable clone of the brush.
|
|||
/// </summary>
|
|||
/// <returns>The immutable clone.</returns>
|
|||
IExperimentalAcrylicMaterial ToImmutable(); |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
public readonly struct ImmutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IEquatable<ImmutableExperimentalAcrylicMaterial> |
|||
{ |
|||
public ImmutableExperimentalAcrylicMaterial(IExperimentalAcrylicMaterial brush) |
|||
{ |
|||
BackgroundSource = brush.BackgroundSource; |
|||
TintColor = brush.TintColor; |
|||
TintOpacity = brush.TintOpacity; |
|||
FallbackColor = brush.FallbackColor; |
|||
MaterialColor = brush.MaterialColor; |
|||
} |
|||
|
|||
public AcrylicBackgroundSource BackgroundSource { get; } |
|||
|
|||
public Color TintColor { get; } |
|||
|
|||
public Color MaterialColor { get; } |
|||
|
|||
public double TintOpacity { get; } |
|||
|
|||
public Color FallbackColor { get; } |
|||
|
|||
public bool Equals(ImmutableExperimentalAcrylicMaterial other) |
|||
{ |
|||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
|||
return |
|||
TintColor == other.TintColor && |
|||
TintOpacity == other.TintOpacity && |
|||
BackgroundSource == other.BackgroundSource && |
|||
FallbackColor == other.FallbackColor && MaterialColor == other.MaterialColor; |
|||
|
|||
} |
|||
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
return obj is ImmutableExperimentalAcrylicMaterial other && Equals(other); |
|||
} |
|||
|
|||
public Color GetEffectiveTintColor() |
|||
{ |
|||
return TintColor; |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
int hash = 17; |
|||
|
|||
hash = (hash * 23) + TintColor.GetHashCode(); |
|||
hash = (hash * 23) + TintOpacity.GetHashCode(); |
|||
hash = (hash * 23) + BackgroundSource.GetHashCode(); |
|||
hash = (hash * 23) + FallbackColor.GetHashCode(); |
|||
hash = (hash * 23) + MaterialColor.GetHashCode(); |
|||
|
|||
return hash; |
|||
} |
|||
} |
|||
|
|||
public static bool operator ==(ImmutableExperimentalAcrylicMaterial left, ImmutableExperimentalAcrylicMaterial right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
public static bool operator !=(ImmutableExperimentalAcrylicMaterial left, ImmutableExperimentalAcrylicMaterial right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
public static class MaterialExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a brush to an immutable brush.
|
|||
/// </summary>
|
|||
/// <param name="material">The brush.</param>
|
|||
/// <returns>
|
|||
/// The result of calling <see cref="IMutableBrush.ToImmutable"/> if the brush is mutable,
|
|||
/// otherwise <paramref name="material"/>.
|
|||
/// </returns>
|
|||
public static IExperimentalAcrylicMaterial ToImmutable(this IExperimentalAcrylicMaterial material) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(material != null); |
|||
|
|||
return (material as IMutableExperimentalAcrylicMaterial)?.ToImmutable() ?? material; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
public interface IDrawingContextWithAcrylicLikeSupport |
|||
{ |
|||
void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect); |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.SceneGraph |
|||
{ |
|||
/// <summary>
|
|||
/// A node in the scene graph which represents a rectangle draw.
|
|||
/// </summary>
|
|||
internal class ExperimentalAcrylicNode : DrawOperation |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RectangleNode"/> class.
|
|||
/// </summary>
|
|||
/// <param name="transform">The transform.</param>
|
|||
/// <param name="rect">The rectangle to draw.</param>
|
|||
/// <param name="boxShadow">The box shadow parameters</param>
|
|||
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
|
|||
public ExperimentalAcrylicNode( |
|||
Matrix transform, |
|||
IExperimentalAcrylicMaterial material, |
|||
RoundedRect rect) |
|||
: base(rect.Rect, transform) |
|||
{ |
|||
Transform = transform; |
|||
Material = material?.ToImmutable(); |
|||
Rect = rect; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform with which the node will be drawn.
|
|||
/// </summary>
|
|||
public Matrix Transform { get; } |
|||
|
|||
public IExperimentalAcrylicMaterial Material { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the rectangle to draw.
|
|||
/// </summary>
|
|||
public RoundedRect Rect { get; } |
|||
|
|||
/// <summary>
|
|||
/// Determines if this draw operation equals another.
|
|||
/// </summary>
|
|||
/// <param name="transform">The transform of the other draw operation.</param>
|
|||
/// <param name="brush">The fill of the other draw operation.</param>
|
|||
/// <param name="rect">The rectangle of the other draw operation.</param>
|
|||
/// <returns>True if the draw operations are the same, otherwise false.</returns>
|
|||
/// <remarks>
|
|||
/// The properties of the other draw operation are passed in as arguments to prevent
|
|||
/// allocation of a not-yet-constructed draw operation object.
|
|||
/// </remarks>
|
|||
public bool Equals(Matrix transform, IExperimentalAcrylicMaterial material, RoundedRect rect) |
|||
{ |
|||
return transform == Transform && |
|||
Equals(material, Material) && |
|||
rect.Equals(Rect); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Render(IDrawingContextImpl context) |
|||
{ |
|||
context.Transform = Transform; |
|||
|
|||
if(context is IDrawingContextWithAcrylicLikeSupport idc) |
|||
{ |
|||
idc.DrawRectangle(Material, Rect); |
|||
} |
|||
else |
|||
{ |
|||
context.DrawRectangle(new ImmutableSolidColorBrush(Material.FallbackColor), null, Rect); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool HitTest(Point p) |
|||
{ |
|||
// TODO: This doesn't respect CornerRadius yet.
|
|||
if (Transform.HasInverse) |
|||
{ |
|||
p *= Transform.Invert(); |
|||
|
|||
if (Material != null) |
|||
{ |
|||
var rect = Rect.Rect; |
|||
return rect.Contains(p); |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 3.2 KiB |
Loading…
Reference in new issue