Skip to content

Commit

Permalink
Merge branch 'master' into disable-autocapitalisation
Browse files Browse the repository at this point in the history
  • Loading branch information
frenzibyte authored Jan 19, 2025
2 parents 7f11dc4 + 8d2f620 commit 02d7e0c
Show file tree
Hide file tree
Showing 46 changed files with 783 additions and 149 deletions.
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
]
},
"nvika": {
"version": "3.0.0",
"version": "4.0.0",
"commands": [
"nvika"
]
Expand Down
1 change: 1 addition & 0 deletions osu-framework.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BPM/@EntryIndexedValue">BPM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CG/@EntryIndexedValue">CG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FBO/@EntryIndexedValue">FBO</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CCL/@EntryIndexedValue">CCL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String>
Expand Down
1 change: 0 additions & 1 deletion osu.Framework.Android/osu.Framework.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
<ProjectReference Include="..\osu.Framework\osu.Framework.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.SDL3-CS.Android" Version="2024.1128.0" />
<PackageReference Include="Xamarin.AndroidX.Window" Version="1.2.0.1" PrivateAssets="compile" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ private void scrollTo(float position, float scrollContentHeight, float extension
AddStep($"scroll to {position}", () =>
{
scrollContainer.ScrollTo(position, false);
immediateScrollPosition = scrollContainer.Current;
immediateScrollPosition = (float)scrollContainer.Current;
});

AddAssert($"immediately scrolled to {clampedTarget}", () => Precision.AlmostEquals(clampedTarget, immediateScrollPosition, 1));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osuTK;
using osuTK.Graphics;

namespace osu.Framework.Tests.Visual.Containers
{
public partial class TestSceneScrollContainerDoublePrecision : ManualInputManagerTestScene
{
private const float item_height = 5000;
private const int item_count = 8000;

private ScrollContainer<Drawable> scrollContainer = null!;

[SetUp]
public void Setup() => Schedule(Clear);

[Test]
public void TestStandard()
{
AddStep("Create scroll container", () =>
{
Add(scrollContainer = new BasicScrollContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ScrollbarVisible = true,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.7f, 0.9f),
});

for (int i = 0; i < item_count; i++)
{
scrollContainer.Add(new BoxWithDouble
{
Colour = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1),
RelativeSizeAxes = Axes.X,
Height = item_height,
Y = i * item_height,
});
}
});

scrollIntoView(item_count - 2);
scrollIntoView(item_count - 1);
}

[Test]
public void TestDoublePrecision()
{
AddStep("Create scroll container", () =>
{
Add(scrollContainer = new DoubleScrollContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ScrollbarVisible = true,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.7f, 0.9f),
});

for (int i = 0; i < item_count; i++)
{
scrollContainer.Add(new BoxWithDouble
{
Colour = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1),
RelativeSizeAxes = Axes.X,
Height = item_height,
DoubleLocation = i * item_height,
});
}
});

scrollIntoView(item_count - 2);
scrollIntoView(item_count - 1);
}

private void scrollIntoView(int index)
{
AddStep($"scroll {index} into view", () => scrollContainer.ScrollIntoView(scrollContainer.ChildrenOfType<BoxWithDouble>().Skip(index).First()));
AddUntilStep($"{index} is visible", () => !scrollContainer.ChildrenOfType<BoxWithDouble>().Skip(index).First().IsMaskedAway);
}

public partial class DoubleScrollContainer : BasicScrollContainer
{
private readonly Container<BoxWithDouble> layoutContent;

public override void Add(Drawable drawable)
{
if (drawable is not BoxWithDouble boxWithDouble)
throw new InvalidOperationException();

Add(boxWithDouble);
}

public void Add(BoxWithDouble drawable)
{
if (drawable is not BoxWithDouble boxWithDouble)
throw new InvalidOperationException();

layoutContent.Height = (float)Math.Max(layoutContent.Height, boxWithDouble.DoubleLocation + boxWithDouble.DrawHeight);
layoutContent.Add(drawable);
}

public DoubleScrollContainer()
{
// Managing our own custom layout within ScrollContent causes feedback with internal ScrollContainer calculations,
// so we must maintain one level of separation from ScrollContent.
base.Add(layoutContent = new Container<BoxWithDouble>
{
RelativeSizeAxes = Axes.X,
});
}

public override double GetChildPosInContent(Drawable d, Vector2 offset)
{
if (d is not BoxWithDouble boxWithDouble)
return base.GetChildPosInContent(d, offset);

return boxWithDouble.DoubleLocation + offset.X;
}

protected override void ApplyCurrentToContent()
{
Debug.Assert(ScrollDirection == Direction.Vertical);

double scrollableExtent = -Current + ScrollableExtent * ScrollContent.RelativeAnchorPosition.Y;

foreach (var d in layoutContent)
d.Y = (float)(d.DoubleLocation + scrollableExtent);
}
}

public partial class BoxWithDouble : Box
{
public double DoubleLocation { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ public void TestManyChildren(bool instant)
AddUntilStep("repeating schedulers removed", () => !scroll.Scheduler.HasPendingTasks);
}

// Fails once in a blue moon due to loose (but maybe-not-loose-enough) timing requirements. If we break things, it will fail every time so this is fine.
[TestCase(false)]
[TestCase(true)]
[FlakyTest]
public void TestManyChildrenFunction(bool instant)
{
AddStep("create children", () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ private BasicRearrangeableListItem<int>.Button getDragger(int index)

private partial class TestRearrangeableList : BasicRearrangeableListContainer<int>
{
public float ScrollPosition => ScrollContainer.Current;
public float ScrollPosition => (float)ScrollContainer.Current;

public new IReadOnlyDictionary<int, RearrangeableListItem<int>> ItemMap => base.ItemMap;

Expand Down
34 changes: 34 additions & 0 deletions osu.Framework.Tests/Visual/UserInterface/TestSceneTextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,40 @@ public void TestSetTextSelection()
AddAssert("nothing selected", () => textBox.SelectedText == string.Empty);
}

[Test]
public void TestTextChangedDuringDoubleClickDrag()
{
InsertableTextBox textBox = null;

AddStep("add textbox", () =>
{
textBoxes.Add(textBox = new InsertableTextBox
{
Size = new Vector2(300, 40),
Text = "initial text",
});
});

AddStep("click on textbox", () =>
{
InputManager.MoveMouseTo(textBox);
InputManager.Click(MouseButton.Left);
});

AddStep("set text", () => textBox.Text = "aaaaaaaaaaaaaaaaaaaa");

AddStep("select word", () =>
{
InputManager.Click(MouseButton.Left);
InputManager.PressButton(MouseButton.Left);
});

AddStep("insert text", () => textBox.InsertString("a"));
AddAssert("text overwritten", () => textBox.Text == "a");
AddStep("start drag", () => InputManager.MoveMouseTo(textBox, new Vector2(-50, 0)));
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
}

[Test]
public void TestSelectAll()
{
Expand Down
90 changes: 12 additions & 78 deletions osu.Framework.iOS/Graphics/Textures/IOSTextureLoaderStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,16 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Accelerate;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Platform.Apple;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using UIKit;

namespace osu.Framework.iOS.Graphics.Textures
{
public class IOSTextureLoaderStore : TextureLoaderStore
internal class IOSTextureLoaderStore : AppleTextureLoaderStore
{
public IOSTextureLoaderStore(IResourceStore<byte[]> store)
: base(store)
Expand All @@ -26,81 +20,21 @@ public IOSTextureLoaderStore(IResourceStore<byte[]> store)

protected override unsafe Image<TPixel> ImageFromStream<TPixel>(Stream stream)
{
using (var nativeData = NSData.FromStream(stream))
using (new NSAutoreleasePool())
{
if (nativeData == null)
throw new ArgumentException($"{nameof(Image)} could not be created from {nameof(stream)}.");

using (var uiImage = UIImage.LoadFromData(nativeData))
{
if (uiImage == null) throw new ArgumentException($"{nameof(Image)} could not be created from {nameof(stream)}.");

int width = (int)uiImage.Size.Width;
int height = (int)uiImage.Size.Height;

var format = new vImage_CGImageFormat
{
BitsPerComponent = 8,
BitsPerPixel = 32,
ColorSpace = CGColorSpace.CreateDeviceRGB().Handle,
// notably, iOS generally uses premultiplied alpha when rendering image to pixels via CGBitmapContext or otherwise,
// but vImage offers using straight alpha directly without any conversion from our side (by specifying Last instead of PremultipliedLast).
BitmapInfo = (CGBitmapFlags)CGImageAlphaInfo.Last,
Decode = null,
RenderingIntent = CGColorRenderingIntent.Default,
};

vImageBuffer accelerateImage = default;

// perform initial call to retrieve preferred alignment and bytes-per-row values for the given image dimensions.
nuint alignment = (nuint)vImageBuffer_Init(&accelerateImage, (uint)height, (uint)width, 32, vImageFlags.NoAllocate);
Debug.Assert(alignment > 0);

// allocate aligned memory region to contain image pixel data.
int bytesPerRow = accelerateImage.BytesPerRow;
int bytesCount = bytesPerRow * accelerateImage.Height;
accelerateImage.Data = (IntPtr)NativeMemory.AlignedAlloc((nuint)bytesCount, alignment);

var result = vImageBuffer_InitWithCGImage(&accelerateImage, &format, null, uiImage.CGImage!.Handle, vImageFlags.NoAllocate);
Debug.Assert(result == vImageError.NoError);
int length = (int)(stream.Length - stream.Position);
var nativeData = NSMutableData.FromLength(length);

var image = new Image<TPixel>(width, height);
byte* data = (byte*)accelerateImage.Data;
var bytesSpan = new Span<byte>(nativeData.MutableBytes.ToPointer(), length);
stream.ReadExactly(bytesSpan);

for (int i = 0; i < height; i++)
{
var imageRow = image.DangerousGetPixelRowMemory(i);
var dataRow = new ReadOnlySpan<TPixel>(&data[bytesPerRow * i], width);
dataRow.CopyTo(imageRow.Span);
}
using var uiImage = UIImage.LoadFromData(nativeData);
if (uiImage == null)
throw new ArgumentException($"{nameof(Image)} could not be created from {nameof(stream)}.");

NativeMemory.AlignedFree(accelerateImage.Data.ToPointer());
return image;
}
var cgImage = new Platform.Apple.Native.CGImage(uiImage.CGImage!.Handle);
return ImageFromCGImage<TPixel>(cgImage);
}
}

#region Accelerate API

[DllImport(Constants.AccelerateLibrary)]
private static extern unsafe vImageError vImageBuffer_Init(vImageBuffer* buf, uint height, uint width, uint pixelBits, vImageFlags flags);

[DllImport(Constants.AccelerateLibrary)]
private static extern unsafe vImageError vImageBuffer_InitWithCGImage(vImageBuffer* buf, vImage_CGImageFormat* format, nfloat* backgroundColour, NativeHandle image, vImageFlags flags);

// ReSharper disable once InconsistentNaming
[StructLayout(LayoutKind.Sequential)]
public unsafe struct vImage_CGImageFormat
{
public uint BitsPerComponent;
public uint BitsPerPixel;
public NativeHandle ColorSpace;
public CGBitmapFlags BitmapInfo;
public uint Version;
public nfloat* Decode;
public CGColorRenderingIntent RenderingIntent;
}

#endregion
}
}
14 changes: 14 additions & 0 deletions osu.Framework/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ protected Game()

protected sealed override void AddInternal(Drawable drawable) => throw new InvalidOperationException($"Use {nameof(Add)} or {nameof(Content)} instead.");

/// <summary>
/// The earliest point of entry during <see cref="GameHost.Run"/> starting execution of a game.
/// This should be used to set up any low level tasks such as exception handling.
/// </summary>
/// <remarks>
/// At this point in execution, only <see cref="GameHost.Storage"/> and <see cref="GameHost.CacheStorage"/> are guaranteed to be valid for use.
/// They are provided as <paramref name="gameStorage"/> and <paramref name="cacheStorage"/> respectively for convenience.
/// </remarks>
/// <param name="gameStorage">The default game storage.</param>
/// <param name="cacheStorage">The default cache storage.</param>
public virtual void SetupLogging(Storage gameStorage, Storage cacheStorage)
{
}

/// <summary>
/// As Load is run post host creation, you can override this method to alter properties of the host before it makes itself visible to the user.
/// </summary>
Expand Down
Loading

0 comments on commit 02d7e0c

Please sign in to comment.