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