Skip to content

Commit

Permalink
Merge branch 'master' into feature/ios-voiceover
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaMorphic authored Feb 26, 2025
2 parents 6d37a96 + abd7a88 commit 254313c
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 33 deletions.
20 changes: 15 additions & 5 deletions src/Avalonia.Base/Media/MediaContext.Compositor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,24 @@ private void SyncCommit(Compositor compositor, bool waitFullRender, bool catchEx
if (AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() == null)
return;

using var _ = NonPumpingLockHelper.Use();
SyncWaitCompositorBatch(compositor, CommitCompositor(compositor), waitFullRender, catchExceptions);
}

private void SyncWaitCompositorBatch(Compositor compositor, CompositionBatch batch,
bool waitFullRender, bool catchExceptions)
{
using var _ = NonPumpingLockHelper.Use();
if (compositor is
{
UseUiThreadForSynchronousCommits: false,
Loop.RunsInBackground: true
})
{
var batch = CommitCompositor(compositor);
(waitFullRender ? batch.Rendered : batch.Processed).Wait();
}
else
{
CommitCompositor(compositor);
compositor.Server.Render(catchExceptions);
}
}
Expand All @@ -132,10 +137,15 @@ public void ImmediateRenderRequested(CompositionTarget target, bool catchExcepti
/// </summary>
public void SyncDisposeCompositionTarget(CompositionTarget compositionTarget)
{
compositionTarget.Dispose();
using var _ = NonPumpingLockHelper.Use();

// TODO: We are sending a dispose command outside of the normal commit cycle and we might
// want to ask the compositor to skip any actual rendering and return the control ASAP
// Not sure if we should do that for background thread rendering since it might affect the animation
// smoothness of other windows

// TODO: introduce a way to skip any actual rendering for other targets and only do a dispose?
SyncCommit(compositionTarget.Compositor, false, true);
var oobBatch = compositionTarget.Compositor.OobDispose(compositionTarget);
SyncWaitCompositorBatch(compositionTarget.Compositor, oobBatch, false, true);
}

/// <summary>
Expand Down
49 changes: 38 additions & 11 deletions src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -679,20 +679,47 @@ private TextLine[] CreateTextLines()

private void UpdateMetrics(TextLineImpl currentLine, ref bool first)
{
_metrics.InkBounds = _metrics.InkBounds.Union(new Rect(new Point(0, _metrics.Bounds.Bottom) + currentLine.InkBounds.Position, currentLine.InkBounds.Size));
_metrics.Bounds = _metrics.Bounds.Union(new Rect(new Point(0, _metrics.Bounds.Bottom) + currentLine.Bounds.Position, currentLine.Bounds.Size));
// 1) Offset each line’s bounding rectangles by the total height so far,
// so we keep an overall bounding box for the entire text block.
var lineTop = _metrics.Height;

_metrics.MinTextWidth = Math.Max(_metrics.MinTextWidth, currentLine.Bounds.Width);
_metrics.MinTextWidth = Math.Max(_metrics.MinTextWidth, currentLine.InkBounds.Width);
// Offset the line's Bounds
var lineBoundsRect = new Rect(
currentLine.Bounds.X,
lineTop + currentLine.Bounds.Y,
currentLine.Bounds.Width,
currentLine.Bounds.Height);

_metrics.Height = _metrics.Bounds.Height;
_metrics.Width = _metrics.InkBounds.Width;
_metrics.WidthIncludingTrailingWhitespace = _metrics.Bounds.Width;
_metrics.Extent = _metrics.InkBounds.Height;
_metrics.OverhangLeading = Math.Max(0, _metrics.Bounds.Left - _metrics.InkBounds.Left);
_metrics.OverhangTrailing = Math.Max(0, _metrics.InkBounds.Right - _metrics.Bounds.Right);
_metrics.OverhangAfter = Math.Max(0, _metrics.InkBounds.Bottom - _metrics.Bounds.Bottom);
_metrics.Bounds = _metrics.Bounds.Union(lineBoundsRect);

// Offset the line's InkBounds
var lineInkRect = new Rect(
currentLine.InkBounds.X,
lineTop + currentLine.InkBounds.Y,
currentLine.InkBounds.Width,
currentLine.InkBounds.Height);

_metrics.InkBounds = _metrics.InkBounds.Union(lineInkRect);

// 2) Accumulate total layout height by adding the line’s Height.
_metrics.Height += currentLine.Height;

// 3) For the layout’s Width and WidthIncludingTrailingWhitespace,
// use the maximum of the line widths rather than the bounding box.
_metrics.Width = Math.Max(_metrics.Width, currentLine.Width);
_metrics.WidthIncludingTrailingWhitespace = Math.Max(_metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace);

// 4) Extent is the max black-pixel extent among lines.
_metrics.Extent = Math.Max(_metrics.Extent, currentLine.Extent);

// 5) We can track min-text-width or overhangs similarly if needed.
_metrics.MinTextWidth = Math.Max(_metrics.MinTextWidth, currentLine.Width);

_metrics.OverhangLeading = Math.Max(_metrics.OverhangLeading, currentLine.OverhangLeading);
_metrics.OverhangTrailing = Math.Max(_metrics.OverhangTrailing, currentLine.OverhangTrailing);
_metrics.OverhangAfter = Math.Max(_metrics.OverhangAfter, currentLine.OverhangAfter);

// 6) Capture the baseline from the first line.
if (first)
{
_metrics.Baseline = currentLine.Baseline;
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Platform/Internal/SlicedStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public override long Seek(long offset, SeekOrigin origin)
if (origin == SeekOrigin.Begin)
Position = offset;
if (origin == SeekOrigin.End)
Position = _from + Length + offset;
Position = Length + offset;
if (origin == SeekOrigin.Current)
Position = Position + offset;
return Position;
Expand Down
53 changes: 40 additions & 13 deletions src/Avalonia.Base/Reactive/LightweightObservableBase.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Threading;
using Avalonia.Threading;

namespace Avalonia.Reactive
{
Expand Down Expand Up @@ -118,32 +118,59 @@ protected void PublishNext(T value)
if (Volatile.Read(ref _observers) != null)
{
IObserver<T>[]? observers = null;
IObserver<T>? singleObserver = null;
int count = 0;

// Optimize for the common case of 1/2/3 observers.
IObserver<T>? observer0 = null;
IObserver<T>? observer1 = null;
IObserver<T>? observer2 = null;
lock (this)
{
if (_observers == null)
{
return;
}
if (_observers.Count == 1)
{
singleObserver = _observers[0];
}
else

count = _observers.Count;
switch (count)
{
observers = _observers.ToArray();
case 3:
observer0 = _observers[0];
observer1 = _observers[1];
observer2 = _observers[2];
break;
case 2:
observer0 = _observers[0];
observer1 = _observers[1];
break;
case 1:
observer0 = _observers[0];
break;
case 0:
return;
default:
{
observers = ArrayPool<IObserver<T>>.Shared.Rent(count);
_observers.CopyTo(observers);
break;
}
}
}
if (singleObserver != null)

if (observer0 != null)
{
singleObserver.OnNext(value);
observer0.OnNext(value);
observer1?.OnNext(value);
observer2?.OnNext(value);
}
else
else if (observers != null)
{
foreach (var observer in observers!)
for(int i = 0; i < count; i++)
{
observer.OnNext(value);
observers[i].OnNext(value);
}

ArrayPool<IObserver<T>>.Shared.Return(observers);
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/Avalonia.Base/Rendering/Composition/Compositor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,28 @@ static void SerializeServerJobs(BatchStreamWriter writer, List<Action> list, obj
return commit;
}
}

/// <summary>
/// This method submits a composition with a single dispose command outside the normal
/// commit cycle. This is currently used for disposing CompositionTargets since we need to do that ASAP
/// and without affecting the not yet completed composition batch
/// </summary>
internal CompositionBatch OobDispose(CompositionObject obj)
{
using var _ = NonPumpingLockHelper.Use();
obj.Dispose();
var batch = new CompositionBatch();
using (var writer = new BatchStreamWriter(batch.Changes, _batchMemoryPool, _batchObjectPool))
{
writer.WriteObject(ServerCompositor.RenderThreadDisposeStartMarker);
writer.Write(1);
writer.WriteObject(obj.Server);
}

batch.CommittedAt = Server.Clock.Elapsed;
_server.EnqueueBatch(batch);
return batch;
}

internal void RegisterForSerialization(ICompositorSerializable compositionObject)
{
Expand Down
4 changes: 3 additions & 1 deletion tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;

using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Rendering;
Expand All @@ -22,7 +23,8 @@ public void Close_Should_Remove_PointerOver()
{
using var app = UnitTestApplication.Start(new TestServices(
inputManager: new InputManager(),
focusManager: new FocusManager()));
focusManager: new FocusManager(),
renderInterface: new HeadlessPlatformRenderInterface()));

var renderer = new Mock<IHitTester>();
var device = CreatePointerDeviceMock().Object;
Expand Down
29 changes: 29 additions & 0 deletions tests/Avalonia.Base.UnitTests/Platform/SlicedStreamTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.IO;
using Avalonia.Platform.Internal;
using Xunit;

namespace Avalonia.Base.UnitTests;

public class SlicedStreamTests
{
[Theory]
[InlineData(2, SeekOrigin.Begin, 22, 2, 9)]
[InlineData(2, SeekOrigin.Current, 22, 17, 24)]
[InlineData(-2, SeekOrigin.End, 22, 40, 47)]
public void Seek_Works(
long offset,
SeekOrigin origin,
long startingUnderlyingPosition,
long expectedPosition,
long expectedUnderlyingPosition)
{
var memoryStream = new MemoryStream(new byte[1024]);
var slicedStream = new SlicedStream(memoryStream, 7, 42);
memoryStream.Position = startingUnderlyingPosition;

slicedStream.Seek(offset, origin);

Assert.Equal(expectedPosition, slicedStream.Position);
Assert.Equal(expectedUnderlyingPosition, memoryStream.Position);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public void Activation_For_View_Fetcher_Should_Support_When_Activated()
[Fact]
public void Activation_For_View_Fetcher_Should_Support_Windows()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new TestWindowWithWhenActivated();
Assert.False(window.Active);
Expand All @@ -171,7 +171,7 @@ public void Activation_For_View_Fetcher_Should_Support_Windows()
[Fact]
public void Activatable_Window_View_Model_Is_Activated_And_Deactivated()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var viewModel = new ActivatableViewModel();
var window = new ActivatableWindow { ViewModel = viewModel };
Expand Down
2 changes: 2 additions & 0 deletions tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
<EmbeddedResource Include="..\Avalonia.RenderTests\*\*.ttf" />
<None Remove="Fonts\DejaVuSans.ttf" />
<None Remove="Fonts\Manrope-Light.ttf" />
<None Remove="Fonts\WinSymbols3.ttf" />
<EmbeddedResource Include="Fonts\DejaVuSans.ttf" />
<EmbeddedResource Include="Fonts\Manrope-Light.ttf" />
<EmbeddedResource Include="Fonts\WinSymbols3.ttf" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,19 @@ public void Should_Handle_TextStyle_With_Ligature()
}
}

[Fact]
public void Should_Measure_TextLayoutSymbolWithAndWidthIncludingTrailingWhitespace()
{
const string symbolsFont = "resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#Symbols";
using (Start())
{
var textLayout = new TextLayout("\ue971", new Typeface(symbolsFont), 12.0, Brushes.White);

Assert.Equal(new Size(12.0, 12.0), new Size(textLayout.Width, textLayout.Height));
Assert.Equal(12.0, textLayout.WidthIncludingTrailingWhitespace);
}
}

private static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
Expand Down

0 comments on commit 254313c

Please sign in to comment.