committed by
GitHub
78 changed files with 1268 additions and 673 deletions
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,43 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net7.0</TargetFramework> |
||||
|
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier> |
||||
|
<WasmMainJSPath>main.js</WasmMainJSPath> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
||||
|
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver> |
||||
|
<WasmBuildNative>true</WasmBuildNative> |
||||
|
<EmccFlags>-sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0</EmccFlags> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)'=='Release'"> |
||||
|
<RunAOTCompilation>true</RunAOTCompilation> |
||||
|
<PublishTrimmed>true</PublishTrimmed> |
||||
|
<TrimMode>full</TrimMode> |
||||
|
<WasmBuildNative>true</WasmBuildNative> |
||||
|
<InvariantGlobalization>true</InvariantGlobalization> |
||||
|
<EmccCompileOptimizationFlag>-O2</EmccCompileOptimizationFlag> |
||||
|
<EmccLinkOptimizationFlag>-O2</EmccLinkOptimizationFlag> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<TrimmerRootDescriptor Include="Roots.xml" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.csproj" /> |
||||
|
<ProjectReference Include="..\MobileSandbox\MobileSandbox.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<WasmExtraFilesToDeploy Include="index.html" /> |
||||
|
<WasmExtraFilesToDeploy Include="main.js" /> |
||||
|
<WasmExtraFilesToDeploy Include="favicon.ico" /> |
||||
|
<WasmExtraFilesToDeploy Include="Logo.svg" /> |
||||
|
<WasmExtraFilesToDeploy Include="app.css" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<Import Project="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.props" /> |
||||
|
<Import Project="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.targets" /> |
||||
|
</Project> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System.Runtime.Versioning; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Browser; |
||||
|
using MobileSandbox; |
||||
|
|
||||
|
[assembly:SupportedOSPlatform("browser")] |
||||
|
|
||||
|
internal partial class Program |
||||
|
{ |
||||
|
public static async Task Main(string[] args) |
||||
|
{ |
||||
|
await BuildAvaloniaApp() |
||||
|
.StartBrowserAppAsync("out"); |
||||
|
} |
||||
|
|
||||
|
public static AppBuilder BuildAvaloniaApp() |
||||
|
=> AppBuilder.Configure<App>(); |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"profiles": { |
||||
|
"MobileSandbox.Browser": { |
||||
|
"commandName": "Project", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
}, |
||||
|
"applicationUrl": "https://localhost:65312;http://localhost:65313;" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
<linker> |
||||
|
<assembly fullname="MobileSandbox" preserve="All" /> |
||||
|
<assembly fullname="MobileSandbox.Browser" preserve="All" /> |
||||
|
<assembly fullname="Avalonia.Themes.Fluent" preserve="All" /> |
||||
|
<assembly fullname="Avalonia.Themes.Simple" preserve="All" /> |
||||
|
<assembly fullname="Avalonia.Controls.ColorPicker" preserve="All" /> |
||||
|
</linker> |
||||
@ -0,0 +1,56 @@ |
|||||
|
:root { |
||||
|
--sat: env(safe-area-inset-top); |
||||
|
--sar: env(safe-area-inset-right); |
||||
|
--sab: env(safe-area-inset-bottom); |
||||
|
--sal: env(safe-area-inset-left); |
||||
|
} |
||||
|
|
||||
|
#out { |
||||
|
height: 100vh; |
||||
|
width: 100vw |
||||
|
} |
||||
|
|
||||
|
#avalonia-splash { |
||||
|
position: relative; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
color: whitesmoke; |
||||
|
background: #171C2C; |
||||
|
font-family: 'Nunito', sans-serif; |
||||
|
background-position: center; |
||||
|
background-size: cover; |
||||
|
background-repeat: no-repeat; |
||||
|
} |
||||
|
|
||||
|
#avalonia-splash a{ |
||||
|
color: whitesmoke; |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
|
||||
|
.center { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
height: 250px; |
||||
|
} |
||||
|
|
||||
|
.splash-close { |
||||
|
animation: slide 0.5s linear 1s forwards; |
||||
|
} |
||||
|
|
||||
|
@keyframes slide { |
||||
|
0% { |
||||
|
top: 0%; |
||||
|
} |
||||
|
|
||||
|
50% { |
||||
|
opacity: 80%; |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
top: 100%; |
||||
|
overflow: hidden; |
||||
|
opacity: 0; |
||||
|
display: none; |
||||
|
visibility: collapse; |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 172 KiB |
@ -0,0 +1,31 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<!-- Licensed to the .NET Foundation under one or more agreements. --> |
||||
|
<!-- The .NET Foundation licenses this file to you under the MIT license. --> |
||||
|
<html> |
||||
|
|
||||
|
<head> |
||||
|
<title>Mobile Sandbox</title> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<link rel="modulepreload" href="./main.js" /> |
||||
|
<link rel="modulepreload" href="./dotnet.js" /> |
||||
|
<link rel="modulepreload" href="./avalonia.js" /> |
||||
|
<link rel="stylesheet" href="./app.css" /> |
||||
|
</head> |
||||
|
|
||||
|
<body style="margin: 0px"> |
||||
|
<div id="out"> |
||||
|
<div id="avalonia-splash"> |
||||
|
<div class="center"> |
||||
|
<h2>Powered by</h2> |
||||
|
<a class="navbar-brand" href="https://www.avaloniaui.net/" target="_blank"> |
||||
|
<img src="Logo.svg" alt="Avalonia Logo" width="30" height="24" /> |
||||
|
Avalonia |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<script type='module' src="./main.js"></script> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
|
||||
|
import { dotnet } from './dotnet.js' |
||||
|
|
||||
|
const is_browser = typeof window != "undefined"; |
||||
|
if (!is_browser) throw new Error(`Expected to be running in a browser`); |
||||
|
|
||||
|
const dotnetRuntime = await dotnet |
||||
|
.withDiagnosticTracing(false) |
||||
|
.withApplicationArgumentsFromQuery() |
||||
|
.create(); |
||||
|
|
||||
|
const config = dotnetRuntime.getConfig(); |
||||
|
|
||||
|
await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]); |
||||
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"wasmHostProperties": { |
||||
|
"perHostConfig": [ |
||||
|
{ |
||||
|
"name": "browser", |
||||
|
"html-path": "index.html", |
||||
|
"Host": "browser" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Animation.Animators; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Animation; |
||||
|
|
||||
|
partial class Animation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Sets the value of the Animator attached property for a setter.
|
||||
|
/// </summary>
|
||||
|
/// <param name="setter">The animation setter.</param>
|
||||
|
/// <param name="value">The property animator value.</param>
|
||||
|
[Obsolete("CustomAnimatorBase will be removed before 11.0, use InterpolatingAnimator<T>", true)] |
||||
|
public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value) |
||||
|
{ |
||||
|
s_animators[setter] = (value.WrapperType, value.CreateWrapper); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the value of the Animator attached property for a setter.
|
||||
|
/// </summary>
|
||||
|
/// <param name="setter">The animation setter.</param>
|
||||
|
/// <param name="value">The property animator value.</param>
|
||||
|
public static void SetAnimator(IAnimationSetter setter, ICustomAnimator value) |
||||
|
{ |
||||
|
s_animators[setter] = (value.WrapperType, value.CreateWrapper); |
||||
|
} |
||||
|
|
||||
|
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator, Func<IAnimator> Factory)> |
||||
|
Animators = new() |
||||
|
{ |
||||
|
(prop =>(typeof(double).IsAssignableFrom(prop.PropertyType) && typeof(Transform).IsAssignableFrom(prop.OwnerType)), |
||||
|
typeof(TransformAnimator), () => new TransformAnimator()), |
||||
|
(prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator()), |
||||
|
(prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator()), |
||||
|
(prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator()), |
||||
|
(prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator()), |
||||
|
(prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator()), |
||||
|
(prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator()), |
||||
|
(prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator()), |
||||
|
(prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator()), |
||||
|
(prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator()), |
||||
|
(prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator()), |
||||
|
(prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator()), |
||||
|
}; |
||||
|
|
||||
|
static Animation() |
||||
|
{ |
||||
|
RegisterAnimator<IEffect?, EffectAnimator>(); |
||||
|
RegisterAnimator<BoxShadow, BoxShadowAnimator>(); |
||||
|
RegisterAnimator<BoxShadows, BoxShadowsAnimator>(); |
||||
|
RegisterAnimator<IBrush?, BaseBrushAnimator>(); |
||||
|
RegisterAnimator<CornerRadius, CornerRadiusAnimator>(); |
||||
|
RegisterAnimator<Color, ColorAnimator>(); |
||||
|
RegisterAnimator<Vector, VectorAnimator>(); |
||||
|
RegisterAnimator<Point, PointAnimator>(); |
||||
|
RegisterAnimator<Rect, RectAnimator>(); |
||||
|
RegisterAnimator<RelativePoint, RelativePointAnimator>(); |
||||
|
RegisterAnimator<Size, SizeAnimator>(); |
||||
|
RegisterAnimator<Thickness, ThicknessAnimator>(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Registers a <see cref="Animator{T}"/> that can handle
|
||||
|
/// a value type that matches the specified condition.
|
||||
|
/// </summary>
|
||||
|
static void RegisterAnimator<T, TAnimator>() |
||||
|
where TAnimator : Animator<T>, new() |
||||
|
{ |
||||
|
Animators.Insert(0, |
||||
|
(prop => typeof(T).IsAssignableFrom(prop.PropertyType), typeof(TAnimator), () => new TAnimator())); |
||||
|
} |
||||
|
|
||||
|
public static void RegisterCustomAnimator<T, TAnimator>() where TAnimator : InterpolatingAnimator<T>, new() |
||||
|
{ |
||||
|
Animators.Insert(0, (prop => typeof(T).IsAssignableFrom(prop.PropertyType), |
||||
|
typeof(InterpolatingAnimator<T>.AnimatorWrapper), () => new TAnimator().CreateWrapper())); |
||||
|
} |
||||
|
|
||||
|
private static (Type Type, Func<IAnimator> Factory)? GetAnimatorType(AvaloniaProperty property) |
||||
|
{ |
||||
|
foreach (var (condition, type, factory) in Animators) |
||||
|
{ |
||||
|
if (condition(property)) |
||||
|
{ |
||||
|
return (type, factory); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Remote.Protocol.Viewport; |
||||
|
using PlatformPixelFormat = Avalonia.Platform.PixelFormat; |
||||
|
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; |
||||
|
|
||||
|
namespace Avalonia.Controls.Remote.Server |
||||
|
{ |
||||
|
internal partial class RemoteServerTopLevelImpl |
||||
|
{ |
||||
|
private enum FrameStatus |
||||
|
{ |
||||
|
NotRendered, |
||||
|
Rendered, |
||||
|
CopiedToMessage |
||||
|
} |
||||
|
|
||||
|
private sealed class Framebuffer |
||||
|
{ |
||||
|
public static Framebuffer Empty { get; } = new(ProtocolPixelFormat.Rgba8888, default, 1.0); |
||||
|
|
||||
|
private readonly double _dpi; |
||||
|
private readonly PixelSize _frameSize; |
||||
|
private readonly object _dataLock = new(); |
||||
|
private readonly byte[] _data; // for rendering only
|
||||
|
private readonly byte[] _dataCopy; // for messages only
|
||||
|
private FrameStatus _status = FrameStatus.NotRendered; |
||||
|
|
||||
|
public Framebuffer(ProtocolPixelFormat format, Size clientSize, double renderScaling) |
||||
|
{ |
||||
|
var frameSize = PixelSize.FromSize(clientSize, renderScaling); |
||||
|
if (frameSize.Width <= 0 || frameSize.Height <= 0) |
||||
|
frameSize = PixelSize.Empty; |
||||
|
|
||||
|
var bpp = format == ProtocolPixelFormat.Rgb565 ? 2 : 4; |
||||
|
var stride = frameSize.Width * bpp; |
||||
|
var dataLength = Math.Max(0, stride * frameSize.Height); |
||||
|
|
||||
|
_dpi = renderScaling * 96.0; |
||||
|
_frameSize = frameSize; |
||||
|
Format = format; |
||||
|
ClientSize = clientSize; |
||||
|
RenderScaling = renderScaling; |
||||
|
|
||||
|
(Stride, _data, _dataCopy) = dataLength > 0 ? |
||||
|
(stride, new byte[dataLength], new byte[dataLength]) : |
||||
|
(0, Array.Empty<byte>(), Array.Empty<byte>()); |
||||
|
} |
||||
|
|
||||
|
public ProtocolPixelFormat Format { get; } |
||||
|
|
||||
|
public Size ClientSize { get; } |
||||
|
|
||||
|
public double RenderScaling { get; } |
||||
|
|
||||
|
public int Stride { get; } |
||||
|
|
||||
|
public FrameStatus GetStatus() |
||||
|
{ |
||||
|
lock (_dataLock) |
||||
|
return _status; |
||||
|
} |
||||
|
|
||||
|
public ILockedFramebuffer Lock(Action onUnlocked) |
||||
|
{ |
||||
|
var handle = GCHandle.Alloc(_data, GCHandleType.Pinned); |
||||
|
Monitor.Enter(_dataLock); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
return new LockedFramebuffer( |
||||
|
handle.AddrOfPinnedObject(), |
||||
|
_frameSize, |
||||
|
Stride, |
||||
|
new Vector(_dpi, _dpi), |
||||
|
new PlatformPixelFormat((PixelFormatEnum)Format), |
||||
|
() => |
||||
|
{ |
||||
|
handle.Free(); |
||||
|
Array.Copy(_data, _dataCopy, _data.Length); |
||||
|
_status = FrameStatus.Rendered; |
||||
|
Monitor.Exit(_dataLock); |
||||
|
onUnlocked(); |
||||
|
}); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
handle.Free(); |
||||
|
Monitor.Exit(_dataLock); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <remarks>The returned message must NOT be kept around, as it contains a shared buffer.</remarks>
|
||||
|
public FrameMessage ToMessage(long sequenceId) |
||||
|
{ |
||||
|
lock (_dataLock) |
||||
|
_status = FrameStatus.CopiedToMessage; |
||||
|
|
||||
|
return new FrameMessage |
||||
|
{ |
||||
|
SequenceId = sequenceId, |
||||
|
Data = _dataCopy, |
||||
|
Format = Format, |
||||
|
Width = _frameSize.Width, |
||||
|
Height = _frameSize.Height, |
||||
|
Stride = Stride, |
||||
|
DpiX = _dpi, |
||||
|
DpiY = _dpi |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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,121 @@ |
|||||
|
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? _clearButton = 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 (_clearButton is not null) |
||||
|
{ |
||||
|
_clearButton.Click -= clearHandler; |
||||
|
} |
||||
|
_clearButton = e.NameScope.Find<Button>("PART_ClearButton"); |
||||
|
if (_clearButton 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>
|
||||
|
/// Get Contrasted Text Color
|
||||
|
/// </summary>
|
||||
|
/// <param name="brush"></param>
|
||||
|
/// <returns></returns>
|
||||
|
private static IBrush GetTextBrush(IBrush brush) |
||||
|
{ |
||||
|
if (brush is ISolidColorBrush solid) |
||||
|
{ |
||||
|
var color = solid.Color; |
||||
|
var l = ColorHelper.GetRelativeLuminance(color); |
||||
|
|
||||
|
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