committed by
GitHub
261 changed files with 5674 additions and 2937 deletions
@ -0,0 +1,79 @@ |
|||
<UserControl x:Class="ControlCatalog.Pages.ThemePage" |
|||
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" |
|||
xmlns:pages="clr-namespace:ControlCatalog.Pages" |
|||
d:DesignWidth="300" |
|||
mc:Ignorable="d"> |
|||
|
|||
<UserControl.Resources> |
|||
<ResourceDictionary> |
|||
<ResourceDictionary.ThemeDictionaries> |
|||
<ResourceDictionary x:Key="Dark"> |
|||
<SolidColorBrush x:Key="DemoBackground">Black</SolidColorBrush> |
|||
</ResourceDictionary> |
|||
<ResourceDictionary x:Key="Light"> |
|||
<SolidColorBrush x:Key="DemoBackground">White</SolidColorBrush> |
|||
</ResourceDictionary> |
|||
<ResourceDictionary x:Key="{x:Static pages:ThemePage.Pink}"> |
|||
<SolidColorBrush x:Key="DemoBackground">#ffe5ea</SolidColorBrush> |
|||
<SolidColorBrush x:Key="NormalBackgroundBrush" Color="#ffc0cb" /> |
|||
<SolidColorBrush x:Key="PointerOverBackgroundBrush" Color="#ffb3c0" /> |
|||
<SolidColorBrush x:Key="PressedBackgroundBrush" Color="#ff4d6c" /> |
|||
<SolidColorBrush x:Key="NormalBorderBrush" Color="#ff8096" /> |
|||
<SolidColorBrush x:Key="PointerOverBorderBrush" Color="#ff8096" /> |
|||
<SolidColorBrush x:Key="PressedBorderBrush" Color="#ff4d6c" /> |
|||
|
|||
<!-- Override colors for fluent theme --> |
|||
<StaticResource x:Key="ButtonBackground" ResourceKey="NormalBackgroundBrush" /> |
|||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="PointerOverBackgroundBrush" /> |
|||
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="PressedBackgroundBrush" /> |
|||
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="NormalBorderBrush" /> |
|||
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="PointerOverBorderBrush" /> |
|||
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="PressedBorderBrush" /> |
|||
<StaticResource x:Key="TextControlBackground" ResourceKey="NormalBackgroundBrush" /> |
|||
<StaticResource x:Key="TextControlBackgroundPointerOver" ResourceKey="PointerOverBackgroundBrush" /> |
|||
<StaticResource x:Key="TextControlBackgroundFocused" ResourceKey="PointerOverBackgroundBrush" /> |
|||
<StaticResource x:Key="TextControlBorderBrush" ResourceKey="NormalBorderBrush" /> |
|||
<StaticResource x:Key="TextControlBorderBrushPointerOver" ResourceKey="PointerOverBorderBrush" /> |
|||
<StaticResource x:Key="TextControlBorderBrushFocused" ResourceKey="PressedBorderBrush" /> |
|||
<StaticResource x:Key="ComboBoxBackground" ResourceKey="NormalBackgroundBrush" /> |
|||
<StaticResource x:Key="ComboBoxBackgroundPointerOver" ResourceKey="PointerOverBackgroundBrush" /> |
|||
<StaticResource x:Key="ComboBoxBackgroundPressed" ResourceKey="PressedBackgroundBrush" /> |
|||
<StaticResource x:Key="ComboBoxBorderBrush" ResourceKey="NormalBorderBrush" /> |
|||
<StaticResource x:Key="ComboBoxBorderBrushPointerOver" ResourceKey="PointerOverBorderBrush" /> |
|||
<StaticResource x:Key="ComboBoxBorderBrushPressed" ResourceKey="PressedBorderBrush" /> |
|||
<!-- Override colors for default theme --> |
|||
<StaticResource x:Key="ThemeControlMidBrush" ResourceKey="NormalBackgroundBrush" /> |
|||
<StaticResource x:Key="ThemeControlHighBrush" ResourceKey="PressedBackgroundBrush" /> |
|||
<StaticResource x:Key="ThemeBorderLowBrush" ResourceKey="NormalBorderBrush" /> |
|||
<StaticResource x:Key="ThemeBorderMidBrush" ResourceKey="PointerOverBorderBrush" /> |
|||
</ResourceDictionary> |
|||
</ResourceDictionary.ThemeDictionaries> |
|||
</ResourceDictionary> |
|||
</UserControl.Resources> |
|||
|
|||
<ThemeVariantScope x:Name="ThemeVariantScope"> |
|||
<Border Background="{DynamicResource DemoBackground}" |
|||
VerticalAlignment="Top" |
|||
HorizontalAlignment="Left" |
|||
Padding="4" |
|||
CornerRadius="4"> |
|||
<Grid RowDefinitions="Auto, 4, Auto, 4, Auto, 4, Auto" ColumnDefinitions="150, 150"> |
|||
<ComboBox Grid.Column="0" Grid.Row="0" x:Name="Selector" HorizontalAlignment="Stretch"> |
|||
<ComboBox.ItemTemplate> |
|||
<DataTemplate x:DataType="x:String"> |
|||
<TextBlock Text="{Binding TargetNullValue=Unset}" /> |
|||
</DataTemplate> |
|||
</ComboBox.ItemTemplate> |
|||
</ComboBox> |
|||
<TextBlock Grid.Column="0" Grid.Row="2" Text="Username:" VerticalAlignment="Center" /> |
|||
<TextBlock Grid.Column="0" Grid.Row="4" Text="Password:" VerticalAlignment="Center" /> |
|||
<TextBox Grid.Column="1" Grid.Row="2" Watermark="Input here" HorizontalAlignment="Stretch" /> |
|||
<TextBox Grid.Column="1" Grid.Row="4" Watermark="Input here" HorizontalAlignment="Stretch" /> |
|||
<Button Grid.Column="1" Grid.Row="6" Content="Login" HorizontalAlignment="Stretch" /> |
|||
</Grid> |
|||
</Border> |
|||
</ThemeVariantScope> |
|||
</UserControl> |
|||
@ -0,0 +1,37 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class ThemePage : UserControl |
|||
{ |
|||
public static ThemeVariant Pink { get; } = new("Pink", ThemeVariant.Light); |
|||
|
|||
public ThemePage() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
|
|||
var selector = this.FindControl<ComboBox>("Selector")!; |
|||
var themeVariantScope = this.FindControl<ThemeVariantScope>("ThemeVariantScope")!; |
|||
|
|||
selector.Items = new[] |
|||
{ |
|||
ThemeVariant.Default, |
|||
ThemeVariant.Dark, |
|||
ThemeVariant.Light, |
|||
Pink |
|||
}; |
|||
selector.SelectedIndex = 0; |
|||
|
|||
selector.SelectionChanged += (_, _) => |
|||
{ |
|||
if (selector.SelectedItem is ThemeVariant theme) |
|||
{ |
|||
themeVariantScope.RequestedThemeVariant = theme; |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -1,8 +1,9 @@ |
|||
<Application xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
Name="Mobile Sandbox" |
|||
x:Class="MobileSandbox.App"> |
|||
x:Class="MobileSandbox.App" |
|||
RequestedThemeVariant="Dark"> |
|||
<Application.Styles> |
|||
<FluentTheme Mode="Dark" /> |
|||
<FluentTheme /> |
|||
</Application.Styles> |
|||
</Application> |
|||
|
|||
@ -1,5 +1,5 @@ |
|||
<Application xmlns="https://github.com/avaloniaui"> |
|||
<Application.Styles> |
|||
<SimpleTheme Mode="Light" /> |
|||
<SimpleTheme /> |
|||
</Application.Styles> |
|||
</Application> |
|||
|
|||
@ -1,5 +1,5 @@ |
|||
<Application xmlns="https://github.com/avaloniaui"> |
|||
<Application.Styles> |
|||
<SimpleTheme Mode="Light" /> |
|||
<SimpleTheme /> |
|||
</Application.Styles> |
|||
</Application> |
|||
|
|||
@ -0,0 +1,112 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Collections |
|||
{ |
|||
/// <summary>
|
|||
/// Defines extension methods for working with <see cref="AvaloniaList{T}"/>s.
|
|||
/// </summary>
|
|||
public static class AvaloniaDictionaryExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Invokes an action for each item in a collection and subsequently each item added or
|
|||
/// removed from the collection.
|
|||
/// </summary>
|
|||
/// <typeparam name="TKey">The key type of the collection items.</typeparam>
|
|||
/// <typeparam name="TValue">The value type of the collection items.</typeparam>
|
|||
/// <param name="collection">The collection.</param>
|
|||
/// <param name="added">
|
|||
/// An action called initially for each item in the collection and subsequently for each
|
|||
/// item added to the collection. The parameters passed are the index in the collection and
|
|||
/// the item.
|
|||
/// </param>
|
|||
/// <param name="removed">
|
|||
/// An action called for each item removed from the collection. The parameters passed are
|
|||
/// the index in the collection and the item.
|
|||
/// </param>
|
|||
/// <param name="reset">
|
|||
/// An action called when the collection is reset. This will be followed by calls to
|
|||
/// <paramref name="added"/> for each item present in the collection after the reset.
|
|||
/// </param>
|
|||
/// <param name="weakSubscription">
|
|||
/// Indicates if a weak subscription should be used to track changes to the collection.
|
|||
/// </param>
|
|||
/// <returns>A disposable used to terminate the subscription.</returns>
|
|||
internal static IDisposable ForEachItem<TKey, TValue>( |
|||
this IAvaloniaReadOnlyDictionary<TKey, TValue> collection, |
|||
Action<TKey, TValue> added, |
|||
Action<TKey, TValue> removed, |
|||
Action reset, |
|||
bool weakSubscription = false) |
|||
where TKey : notnull |
|||
{ |
|||
void Add(IEnumerable items) |
|||
{ |
|||
foreach (KeyValuePair<TKey, TValue> pair in items) |
|||
{ |
|||
added(pair.Key, pair.Value); |
|||
} |
|||
} |
|||
|
|||
void Remove(IEnumerable items) |
|||
{ |
|||
foreach (KeyValuePair<TKey, TValue> pair in items) |
|||
{ |
|||
removed(pair.Key, pair.Value); |
|||
} |
|||
} |
|||
|
|||
NotifyCollectionChangedEventHandler handler = (_, e) => |
|||
{ |
|||
switch (e.Action) |
|||
{ |
|||
case NotifyCollectionChangedAction.Add: |
|||
Add(e.NewItems!); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Move: |
|||
case NotifyCollectionChangedAction.Replace: |
|||
Remove(e.OldItems!); |
|||
int newIndex = e.NewStartingIndex; |
|||
if(newIndex > e.OldStartingIndex) |
|||
{ |
|||
newIndex -= e.OldItems!.Count; |
|||
} |
|||
Add(e.NewItems!); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Remove: |
|||
Remove(e.OldItems!); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Reset: |
|||
if (reset == null) |
|||
{ |
|||
throw new InvalidOperationException( |
|||
"Reset called on collection without reset handler."); |
|||
} |
|||
|
|||
reset(); |
|||
Add(collection); |
|||
break; |
|||
} |
|||
}; |
|||
|
|||
Add(collection); |
|||
|
|||
if (weakSubscription) |
|||
{ |
|||
return collection.WeakSubscribe(handler); |
|||
} |
|||
else |
|||
{ |
|||
collection.CollectionChanged += handler; |
|||
|
|||
return Disposable.Create(() => collection.CollectionChanged -= handler); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Collections |
|||
{ |
|||
public interface IAvaloniaDictionary<TKey, TValue> |
|||
: IDictionary<TKey, TValue>, |
|||
IAvaloniaReadOnlyDictionary<TKey, TValue>, |
|||
IDictionary |
|||
where TKey : notnull |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using System.ComponentModel; |
|||
|
|||
namespace Avalonia.Collections |
|||
{ |
|||
public interface IAvaloniaReadOnlyDictionary<TKey, TValue> |
|||
: IReadOnlyDictionary<TKey, TValue>, |
|||
INotifyCollectionChanged, |
|||
INotifyPropertyChanged |
|||
where TKey : notnull |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
|
|||
namespace Avalonia.Media.TextFormatting |
|||
{ |
|||
/// <summary>Represents a line break that occurred due to wrapping.</summary>
|
|||
internal sealed class WrappingTextLineBreak : TextLineBreak |
|||
{ |
|||
private List<TextRun>? _remainingRuns; |
|||
|
|||
public WrappingTextLineBreak(TextEndOfLine? textEndOfLine, FlowDirection flowDirection, |
|||
List<TextRun> remainingRuns) |
|||
: base(textEndOfLine, flowDirection, isSplit: true) |
|||
{ |
|||
Debug.Assert(remainingRuns.Count > 0); |
|||
_remainingRuns = remainingRuns; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the remaining runs from this line break, and clears them from this line break.
|
|||
/// </summary>
|
|||
/// <returns>A list of text runs.</returns>
|
|||
public List<TextRun>? AcquireRemainingRuns() |
|||
{ |
|||
var remainingRuns = _remainingRuns; |
|||
_remainingRuns = null; |
|||
return remainingRuns; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server |
|||
{ |
|||
/// <summary>
|
|||
/// A class used to render diagnostic strings (only!), with caching of ASCII glyph runs.
|
|||
/// </summary>
|
|||
internal sealed class DiagnosticTextRenderer |
|||
{ |
|||
private const char FirstChar = (char)32; |
|||
private const char LastChar = (char)126; |
|||
|
|||
private readonly GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1]; |
|||
|
|||
public double GetMaxHeight() |
|||
{ |
|||
var maxHeight = 0.0; |
|||
|
|||
for (var c = FirstChar; c <= LastChar; c++) |
|||
{ |
|||
var height = _runs[c - FirstChar].Size.Height; |
|||
if (height > maxHeight) |
|||
{ |
|||
maxHeight = height; |
|||
} |
|||
} |
|||
|
|||
return maxHeight; |
|||
} |
|||
|
|||
public DiagnosticTextRenderer(IGlyphTypeface typeface, double fontRenderingEmSize) |
|||
{ |
|||
var chars = new char[LastChar - FirstChar + 1]; |
|||
for (var c = FirstChar; c <= LastChar; c++) |
|||
{ |
|||
var index = c - FirstChar; |
|||
chars[index] = c; |
|||
var glyph = typeface.GetGlyph(c); |
|||
_runs[index] = new GlyphRun(typeface, fontRenderingEmSize, chars.AsMemory(index, 1), new[] { glyph }); |
|||
} |
|||
} |
|||
|
|||
public Size MeasureAsciiText(ReadOnlySpan<char> text) |
|||
{ |
|||
var width = 0.0; |
|||
var height = 0.0; |
|||
|
|||
foreach (var c in text) |
|||
{ |
|||
var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' '; |
|||
var run = _runs[effectiveChar - FirstChar]; |
|||
width += run.Size.Width; |
|||
height = Math.Max(height, run.Size.Height); |
|||
} |
|||
|
|||
return new Size(width, height); |
|||
} |
|||
|
|||
public void DrawAsciiText(IDrawingContextImpl context, ReadOnlySpan<char> text, IBrush foreground) |
|||
{ |
|||
var offset = 0.0; |
|||
var originalTransform = context.Transform; |
|||
|
|||
foreach (var c in text) |
|||
{ |
|||
var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' '; |
|||
var run = _runs[effectiveChar - FirstChar]; |
|||
context.Transform = originalTransform * Matrix.CreateTranslation(offset, 0.0); |
|||
context.DrawGlyphRun(foreground, run.PlatformImpl); |
|||
offset += run.Size.Width; |
|||
} |
|||
|
|||
context.Transform = originalTransform; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Globalization; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
/// <summary>
|
|||
/// Represents a simple time graph for diagnostics purpose, used to show layout and render times.
|
|||
/// </summary>
|
|||
internal sealed class FrameTimeGraph |
|||
{ |
|||
private const double HeaderPadding = 2.0; |
|||
|
|||
private readonly IPlatformRenderInterface _renderInterface; |
|||
private readonly ImmutableSolidColorBrush _borderBrush; |
|||
private readonly ImmutablePen _graphPen; |
|||
private readonly double[] _frameValues; |
|||
private readonly Size _size; |
|||
private readonly Size _headerSize; |
|||
private readonly Size _graphSize; |
|||
private readonly double _defaultMaxY; |
|||
private readonly string _title; |
|||
private readonly DiagnosticTextRenderer _textRenderer; |
|||
|
|||
private int _startFrameIndex; |
|||
private int _frameCount; |
|||
|
|||
public Size Size |
|||
=> _size; |
|||
|
|||
public FrameTimeGraph(int maxFrames, Size size, double defaultMaxY, string title, |
|||
DiagnosticTextRenderer textRenderer) |
|||
{ |
|||
Debug.Assert(maxFrames >= 1); |
|||
Debug.Assert(size.Width > 0.0); |
|||
Debug.Assert(size.Height > 0.0); |
|||
|
|||
_renderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>(); |
|||
_borderBrush = new ImmutableSolidColorBrush(0x80808080); |
|||
_graphPen = new ImmutablePen(Brushes.Blue); |
|||
_frameValues = new double[maxFrames]; |
|||
_size = size; |
|||
_headerSize = new Size(size.Width, textRenderer.GetMaxHeight() + HeaderPadding * 2.0); |
|||
_graphSize = new Size(size.Width, size.Height - _headerSize.Height); |
|||
_defaultMaxY = defaultMaxY; |
|||
_title = title; |
|||
_textRenderer = textRenderer; |
|||
} |
|||
|
|||
public void AddFrameValue(double value) |
|||
{ |
|||
if (_frameCount < _frameValues.Length) |
|||
{ |
|||
_frameValues[_startFrameIndex + _frameCount] = value; |
|||
++_frameCount; |
|||
} |
|||
else |
|||
{ |
|||
// overwrite oldest value
|
|||
_frameValues[_startFrameIndex] = value; |
|||
if (++_startFrameIndex == _frameValues.Length) |
|||
{ |
|||
_startFrameIndex = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Reset() |
|||
{ |
|||
_startFrameIndex = 0; |
|||
_frameCount = 0; |
|||
} |
|||
|
|||
public void Render(IDrawingContextImpl context) |
|||
{ |
|||
var originalTransform = context.Transform; |
|||
context.PushClip(new Rect(_size)); |
|||
|
|||
context.DrawRectangle(_borderBrush, null, new RoundedRect(new Rect(_size))); |
|||
context.DrawRectangle(_borderBrush, null, new RoundedRect(new Rect(_headerSize))); |
|||
|
|||
context.Transform = originalTransform * Matrix.CreateTranslation(HeaderPadding, HeaderPadding); |
|||
_textRenderer.DrawAsciiText(context, _title.AsSpan(), Brushes.Black); |
|||
|
|||
if (_frameCount > 0) |
|||
{ |
|||
var (min, avg, max) = GetYValues(); |
|||
|
|||
DrawLabelledValue(context, "Min", min, originalTransform, _headerSize.Width * 0.19); |
|||
DrawLabelledValue(context, "Avg", avg, originalTransform, _headerSize.Width * 0.46); |
|||
DrawLabelledValue(context, "Max", max, originalTransform, _headerSize.Width * 0.73); |
|||
|
|||
context.Transform = originalTransform * Matrix.CreateTranslation(0.0, _headerSize.Height); |
|||
context.DrawGeometry(null, _graphPen, BuildGraphGeometry(Math.Max(max, _defaultMaxY))); |
|||
} |
|||
|
|||
context.Transform = originalTransform; |
|||
context.PopClip(); |
|||
} |
|||
|
|||
private void DrawLabelledValue(IDrawingContextImpl context, string label, double value, in Matrix originalTransform, |
|||
double left) |
|||
{ |
|||
context.Transform = originalTransform * Matrix.CreateTranslation(left + HeaderPadding, HeaderPadding); |
|||
|
|||
var brush = value <= _defaultMaxY ? Brushes.Black : Brushes.Red; |
|||
|
|||
#if NET6_0_OR_GREATER
|
|||
Span<char> buffer = stackalloc char[24]; |
|||
buffer.TryWrite(CultureInfo.InvariantCulture, $"{label}: {value,5:F2}ms", out var charsWritten); |
|||
_textRenderer.DrawAsciiText(context, buffer.Slice(0, charsWritten), brush); |
|||
#else
|
|||
var text = FormattableString.Invariant($"{label}: {value,5:F2}ms"); |
|||
_textRenderer.DrawAsciiText(context, text.AsSpan(), brush); |
|||
#endif
|
|||
} |
|||
|
|||
private IStreamGeometryImpl BuildGraphGeometry(double maxY) |
|||
{ |
|||
Debug.Assert(_frameCount > 0); |
|||
|
|||
var graphGeometry = _renderInterface.CreateStreamGeometry(); |
|||
using var geometryContext = graphGeometry.Open(); |
|||
|
|||
var xRatio = _graphSize.Width / _frameValues.Length; |
|||
var yRatio = _graphSize.Height / maxY; |
|||
|
|||
geometryContext.BeginFigure(new Point(0.0, _graphSize.Height - GetFrameValue(0) * yRatio), false); |
|||
|
|||
for (var i = 1; i < _frameCount; ++i) |
|||
{ |
|||
var x = Math.Round(i * xRatio); |
|||
var y = _graphSize.Height - GetFrameValue(i) * yRatio; |
|||
geometryContext.LineTo(new Point(x, y)); |
|||
} |
|||
|
|||
geometryContext.EndFigure(false); |
|||
return graphGeometry; |
|||
} |
|||
|
|||
private (double Min, double Average, double Max) GetYValues() |
|||
{ |
|||
Debug.Assert(_frameCount > 0); |
|||
|
|||
var min = double.MaxValue; |
|||
var max = double.MinValue; |
|||
var total = 0.0; |
|||
|
|||
for (var i = 0; i < _frameCount; ++i) |
|||
{ |
|||
var y = GetFrameValue(i); |
|||
|
|||
total += y; |
|||
|
|||
if (y < min) |
|||
{ |
|||
min = y; |
|||
} |
|||
|
|||
if (y > max) |
|||
{ |
|||
max = y; |
|||
} |
|||
} |
|||
|
|||
return (min, total / _frameCount, max); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private double GetFrameValue(int frameOffset) |
|||
=> _frameValues[(_startFrameIndex + frameOffset) % _frameValues.Length]; |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a single layout pass timing.
|
|||
/// </summary>
|
|||
/// <param name="PassCounter">The number of the layout pass.</param>
|
|||
/// <param name="Elapsed">The elapsed time during the layout pass.</param>
|
|||
internal readonly record struct LayoutPassTiming(int PassCounter, TimeSpan Elapsed); |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Rendering; |
|||
|
|||
/// <summary>
|
|||
/// Represents the various types of overlays that can be drawn by a renderer.
|
|||
/// </summary>
|
|||
[Flags] |
|||
public enum RendererDebugOverlays |
|||
{ |
|||
/// <summary>
|
|||
/// Do not draw any overlay.
|
|||
/// </summary>
|
|||
None = 0, |
|||
|
|||
/// <summary>
|
|||
/// Draw a FPS counter.
|
|||
/// </summary>
|
|||
Fps = 1 << 0, |
|||
|
|||
/// <summary>
|
|||
/// Draw invalidated rectangles each frame.
|
|||
/// </summary>
|
|||
DirtyRects = 1 << 1, |
|||
|
|||
/// <summary>
|
|||
/// Draw a graph of past layout times.
|
|||
/// </summary>
|
|||
LayoutTimeGraph = 1 << 2, |
|||
|
|||
/// <summary>
|
|||
/// Draw a graph of past render times.
|
|||
/// </summary>
|
|||
RenderTimeGraph = 1 << 3 |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using System.ComponentModel; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Manages configurable diagnostics that can be displayed by a renderer.
|
|||
/// </summary>
|
|||
public class RendererDiagnostics : INotifyPropertyChanged |
|||
{ |
|||
private RendererDebugOverlays _debugOverlays; |
|||
private LayoutPassTiming _lastLayoutPassTiming; |
|||
private PropertyChangedEventArgs? _debugOverlaysChangedEventArgs; |
|||
private PropertyChangedEventArgs? _lastLayoutPassTimingChangedEventArgs; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets which debug overlays are displayed by the renderer.
|
|||
/// </summary>
|
|||
public RendererDebugOverlays DebugOverlays |
|||
{ |
|||
get => _debugOverlays; |
|||
set |
|||
{ |
|||
if (_debugOverlays != value) |
|||
{ |
|||
_debugOverlays = value; |
|||
OnPropertyChanged(_debugOverlaysChangedEventArgs ??= new(nameof(DebugOverlays))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the last layout pass timing that the renderer may display.
|
|||
/// </summary>
|
|||
internal LayoutPassTiming LastLayoutPassTiming |
|||
{ |
|||
get => _lastLayoutPassTiming; |
|||
set |
|||
{ |
|||
if (!_lastLayoutPassTiming.Equals(value)) |
|||
{ |
|||
_lastLayoutPassTiming = value; |
|||
OnPropertyChanged(_lastLayoutPassTimingChangedEventArgs ??= new(nameof(LastLayoutPassTiming))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public event PropertyChangedEventHandler? PropertyChanged; |
|||
|
|||
/// <summary>
|
|||
/// Called when a property changes on the object.
|
|||
/// </summary>
|
|||
/// <param name="args">The property change details.</param>
|
|||
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) |
|||
=> PropertyChanged?.Invoke(this, args); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Metadata; |
|||
|
|||
namespace Avalonia.Styling; |
|||
|
|||
/// <summary>
|
|||
/// Interface for an application host element with a root theme variant.
|
|||
/// </summary>
|
|||
[Unstable] |
|||
public interface IGlobalThemeVariantProvider : IResourceHost |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the UI theme variant that is used by the control (and its child elements) for resource determination.
|
|||
/// </summary>
|
|||
ThemeVariant ActualThemeVariant { get; } |
|||
|
|||
/// <summary>
|
|||
/// Raised when the theme variant is changed on the element or an ancestor of the element.
|
|||
/// </summary>
|
|||
event EventHandler? ActualThemeVariantChanged; |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Text; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Styling; |
|||
|
|||
/// <summary>
|
|||
/// Specifies a UI theme variant that should be used for the
|
|||
/// </summary>
|
|||
[TypeConverter(typeof(ThemeVariantTypeConverter))] |
|||
public sealed record ThemeVariant |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="ThemeVariant"/>
|
|||
/// </summary>
|
|||
/// <param name="key">Key of the theme variant by which variants are compared.</param>
|
|||
/// <param name="inheritVariant">Reference to a theme variant which should be used, if resource wasn't found for the requested variant.</param>
|
|||
/// <exception cref="ArgumentException">Thrown if inheritVariant is a reference to the <see cref="ThemeVariant.Default"/> which is ambiguous value to inherit.</exception>
|
|||
/// <exception cref="ArgumentNullException">Thrown if key is null.</exception>
|
|||
public ThemeVariant(object key, ThemeVariant? inheritVariant) |
|||
{ |
|||
Key = key ?? throw new ArgumentNullException(nameof(key)); |
|||
InheritVariant = inheritVariant; |
|||
|
|||
if (inheritVariant == Default) |
|||
{ |
|||
throw new ArgumentException("Inheriting default theme variant is not supported.", nameof(inheritVariant)); |
|||
} |
|||
} |
|||
|
|||
private ThemeVariant(object key) |
|||
{ |
|||
Key = key; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Key of the theme variant by which variants are compared.
|
|||
/// </summary>
|
|||
public object Key { get; } |
|||
|
|||
/// <summary>
|
|||
/// Reference to a theme variant which should be used, if resource wasn't found for the requested variant.
|
|||
/// </summary>
|
|||
public ThemeVariant? InheritVariant { get; } |
|||
|
|||
/// <summary>
|
|||
/// Inherit theme variant from the parent. If set on Application, system theme is inherited.
|
|||
/// Using Default as the ResourceDictionary.Key marks this dictionary as a fallback in case the theme variant or resource key is not found in other theme dictionaries.
|
|||
/// </summary>
|
|||
public static ThemeVariant Default { get; } = new(nameof(Default)); |
|||
|
|||
/// <summary>
|
|||
/// Use the Light theme variant.
|
|||
/// </summary>
|
|||
public static ThemeVariant Light { get; } = new(nameof(Light)); |
|||
|
|||
/// <summary>
|
|||
/// Use the Dark theme variant.
|
|||
/// </summary>
|
|||
public static ThemeVariant Dark { get; } = new(nameof(Dark)); |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return Key.ToString() ?? $"ThemeVariant {{ Key = {Key} }}"; |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
return Key.GetHashCode(); |
|||
} |
|||
|
|||
public bool Equals(ThemeVariant? other) |
|||
{ |
|||
return Key == other?.Key; |
|||
} |
|||
|
|||
public static explicit operator ThemeVariant(PlatformThemeVariant themeVariant) |
|||
{ |
|||
return themeVariant switch |
|||
{ |
|||
PlatformThemeVariant.Light => Light, |
|||
PlatformThemeVariant.Dark => Dark, |
|||
_ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null) |
|||
}; |
|||
} |
|||
|
|||
public static explicit operator PlatformThemeVariant?(ThemeVariant themeVariant) |
|||
{ |
|||
if (themeVariant == Light) |
|||
{ |
|||
return PlatformThemeVariant.Light; |
|||
} |
|||
else if (themeVariant == Dark) |
|||
{ |
|||
return PlatformThemeVariant.Dark; |
|||
} |
|||
else if (themeVariant.InheritVariant is { } inheritVariant) |
|||
{ |
|||
return (PlatformThemeVariant?)inheritVariant; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Styling; |
|||
|
|||
public class ThemeVariantTypeConverter : TypeConverter |
|||
{ |
|||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) |
|||
{ |
|||
return sourceType == typeof(string); |
|||
} |
|||
|
|||
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) |
|||
{ |
|||
return value switch |
|||
{ |
|||
nameof(ThemeVariant.Default) => ThemeVariant.Default, |
|||
nameof(ThemeVariant.Light) => ThemeVariant.Light, |
|||
nameof(ThemeVariant.Dark) => ThemeVariant.Dark, |
|||
_ => throw new NotSupportedException("ThemeVariant type converter supports only build in variants. For custom variants please use x:Static markup extension.") |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
|
|||
namespace Avalonia.Utilities; |
|||
|
|||
/// <summary>
|
|||
/// Allows using <see cref="Stopwatch"/> as timestamps without allocating.
|
|||
/// </summary>
|
|||
/// <remarks>Equivalent to Stopwatch.GetElapsedTime in .NET 7.</remarks>
|
|||
internal static class StopwatchHelper |
|||
{ |
|||
private static readonly double s_timestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; |
|||
|
|||
public static TimeSpan GetElapsedTime(long startingTimestamp) |
|||
=> GetElapsedTime(startingTimestamp, Stopwatch.GetTimestamp()); |
|||
|
|||
public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) |
|||
=> new((long)((endingTimestamp - startingTimestamp) * s_timestampToTicks)); |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> |
|||
<PackageId>Avalonia.Controls.ItemsRepeater</PackageId> |
|||
<RootNamespace>Avalonia</RootNamespace> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
|||
</ItemGroup> |
|||
<Import Project="..\..\build\EmbedXaml.props" /> |
|||
<Import Project="..\..\build\BuildTargets.targets" /> |
|||
<!--<Import Project="..\..\build\ApiDiff.props" />--> |
|||
<Import Project="..\..\build\NullableEnable.props" /> |
|||
<Import Project="..\..\build\TrimmingEnable.props" /> |
|||
<Import Project="..\..\build\DevAnalyzers.props" /> |
|||
</Project> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue