4 changed files with 153 additions and 94 deletions
@ -0,0 +1,21 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:controls="using:Avalonia.Diagnostics.Controls" |
|||
x:ClassModifier="internal"> |
|||
<Design.PreviewWith> |
|||
<controls:BrushEditor /> |
|||
</Design.PreviewWith> |
|||
|
|||
<Style Selector=":is(controls|BrushEditor)"> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Grid> |
|||
<Button Theme="{StaticResource SimpleTextBoxClearButtonTheme}" |
|||
x:Name="PART_ClearButton" |
|||
HorizontalAlignment="Right" |
|||
VerticalContentAlignment="Center"/> |
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,131 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Metadata; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
|
|||
namespace Avalonia.Diagnostics.Controls |
|||
{ |
|||
[TemplatePart("PART_ClearButton", typeof(Button))] |
|||
partial class BrushEditor : TemplatedControl |
|||
{ |
|||
private readonly EventHandler<RoutedEventArgs> clearHandler; |
|||
private Button? _cleraButton = default; |
|||
private readonly ColorView _colorView = new() |
|||
{ |
|||
HexInputAlphaPosition = AlphaComponentPosition.Leading, // Always match XAML
|
|||
}; |
|||
|
|||
|
|||
public BrushEditor() |
|||
{ |
|||
FlyoutBase.SetAttachedFlyout(this, new Flyout { Content = _colorView }); |
|||
_colorView.ColorChanged += (_, e) => Brush = new ImmutableSolidColorBrush(e.NewColor); |
|||
clearHandler = (s, e) => Brush = default; |
|||
} |
|||
|
|||
protected override Type StyleKeyOverride => typeof(BrushEditor); |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
if (_cleraButton is not null) |
|||
{ |
|||
_cleraButton.Click -= clearHandler; |
|||
} |
|||
_cleraButton = e.NameScope.Find<Button>("PART_ClearButton"); |
|||
if (_cleraButton is Button button) |
|||
{ |
|||
button.Click += clearHandler; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Brush" /> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<BrushEditor, IBrush?> BrushProperty = |
|||
AvaloniaProperty.RegisterDirect<BrushEditor, IBrush?>( |
|||
nameof(Brush), o => o.Brush, (o, v) => o.Brush = v); |
|||
|
|||
private IBrush? _brush; |
|||
|
|||
public IBrush? Brush |
|||
{ |
|||
get => _brush; |
|||
set => SetAndRaise(BrushProperty, ref _brush, value); |
|||
} |
|||
|
|||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|||
{ |
|||
base.OnPropertyChanged(change); |
|||
|
|||
if (change.Property == BrushProperty) |
|||
{ |
|||
if (Brush is ISolidColorBrush scb) |
|||
{ |
|||
_colorView.Color = scb.Color; |
|||
} |
|||
ToolTip.SetTip(this, Brush?.GetType().Name ?? "(null)"); |
|||
InvalidateVisual(); |
|||
} |
|||
} |
|||
|
|||
protected override void OnPointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
base.OnPointerPressed(e); |
|||
|
|||
FlyoutBase.ShowAttachedFlyout(this); |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
base.Render(context); |
|||
|
|||
var brush = Brush ?? Brushes.Black; |
|||
|
|||
context.FillRectangle(brush, Bounds); |
|||
|
|||
var text = (Brush as ISolidColorBrush)?.Color.ToString() ?? "(null)"; |
|||
|
|||
var ft = new FormattedText(text, |
|||
CultureInfo.CurrentCulture, |
|||
FlowDirection.LeftToRight, |
|||
Typeface.Default, |
|||
10, |
|||
GetTextBrush(brush)); |
|||
|
|||
context.DrawText(ft, |
|||
new Point(Bounds.Width / 2 - ft.Width / 2, Bounds.Height / 2 - ft.Height / 2)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// <see cref="https://stackoverflow.com/questions/6763032/how-to-pick-a-background-color-depending-on-font-color-to-have-proper-contrast">How to pick a background color depending on font color to have proper contrast</see>/>
|
|||
/// </summary>
|
|||
/// <param name="brush"></param>
|
|||
/// <returns></returns>
|
|||
private static IBrush GetTextBrush(IBrush brush) |
|||
{ |
|||
if (brush is ISolidColorBrush solid) |
|||
{ |
|||
var color = solid.Color; |
|||
|
|||
|
|||
double R = color.R / 255.0; |
|||
double G = color.G / 255.0; |
|||
double B = color.B / 255.0; |
|||
|
|||
R = Math.Pow((R + 0.055) / 1.055, 2.4); |
|||
G = Math.Pow((G + 0.055) / 1.055, 2.4); |
|||
B = Math.Pow((B + 0.055) / 1.055, 2.4); |
|||
var l = 0.2126 * R + 0.7152 * G + 0.0722 * B; |
|||
|
|||
return l < 0.5 ? Brushes.White : Brushes.Black; |
|||
} |
|||
return Brushes.White; |
|||
} |
|||
} |
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
using System.Globalization; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
|
|||
namespace Avalonia.Diagnostics.Controls |
|||
{ |
|||
internal sealed class BrushEditor : Control |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Brush" /> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<BrushEditor, IBrush?> BrushProperty = |
|||
AvaloniaProperty.RegisterDirect<BrushEditor, IBrush?>( |
|||
nameof(Brush), o => o.Brush, (o, v) => o.Brush = v); |
|||
|
|||
private IBrush? _brush; |
|||
|
|||
public IBrush? Brush |
|||
{ |
|||
get => _brush; |
|||
set => SetAndRaise(BrushProperty, ref _brush, value); |
|||
} |
|||
|
|||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|||
{ |
|||
base.OnPropertyChanged(change); |
|||
|
|||
if (change.Property == BrushProperty) |
|||
{ |
|||
switch (Brush) |
|||
{ |
|||
case ISolidColorBrush scb: |
|||
{ |
|||
var colorView = new ColorView |
|||
{ |
|||
HexInputAlphaPosition = AlphaComponentPosition.Leading, // Always match XAML
|
|||
Color = scb.Color, |
|||
}; |
|||
|
|||
colorView.ColorChanged += (_, e) => Brush = new ImmutableSolidColorBrush(e.NewColor); |
|||
|
|||
FlyoutBase.SetAttachedFlyout(this, new Flyout { Content = colorView }); |
|||
ToolTip.SetTip(this, $"{scb.Color} ({Brush.GetType().Name})"); |
|||
|
|||
break; |
|||
} |
|||
|
|||
default: |
|||
|
|||
FlyoutBase.SetAttachedFlyout(this, null); |
|||
ToolTip.SetTip(this, Brush?.GetType().Name ?? "(null)"); |
|||
|
|||
break; |
|||
} |
|||
|
|||
InvalidateVisual(); |
|||
} |
|||
} |
|||
|
|||
protected override void OnPointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
base.OnPointerPressed(e); |
|||
|
|||
FlyoutBase.ShowAttachedFlyout(this); |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
base.Render(context); |
|||
|
|||
if (Brush != null) |
|||
{ |
|||
context.FillRectangle(Brush, Bounds); |
|||
} |
|||
else |
|||
{ |
|||
context.FillRectangle(Brushes.Black, Bounds); |
|||
|
|||
var ft = new FormattedText("(null)", |
|||
CultureInfo.CurrentCulture, |
|||
FlowDirection.LeftToRight, |
|||
Typeface.Default, |
|||
10, |
|||
Brushes.White); |
|||
|
|||
context.DrawText(ft, |
|||
new Point(Bounds.Width / 2 - ft.Width / 2, Bounds.Height / 2 - ft.Height / 2)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue