mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-13 11:20:31 +00:00
Merge branch 'master' into better-SDL-pen-handling
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nvika": {
|
||||
"version": "3.0.0",
|
||||
"version": "4.0.0",
|
||||
"commands": [
|
||||
"nvika"
|
||||
]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -525,7 +525,7 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
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));
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,8 +101,10 @@ namespace osu.Framework.Tests.Visual.Drawables
|
||||
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", () =>
|
||||
|
||||
@@ -467,7 +467,7 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -95,7 +95,15 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
|
||||
textBoxes.Add(new CustomTextBox
|
||||
{
|
||||
Text = @"Custom textbox",
|
||||
PlaceholderText = "Custom textbox",
|
||||
Size = new Vector2(500, 30),
|
||||
TabbableContentContainer = textBoxes
|
||||
});
|
||||
|
||||
textBoxes.Add(new BasicTextBox
|
||||
{
|
||||
InputProperties = new TextInputProperties(TextInputType.Text, AutoCapitalisation: true),
|
||||
Text = "Auto-capitalised textbox",
|
||||
Size = new Vector2(500, 30),
|
||||
TabbableContentContainer = textBoxes
|
||||
});
|
||||
@@ -122,8 +130,9 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
TabbableContentContainer = otherTextBoxes
|
||||
});
|
||||
|
||||
otherTextBoxes.Add(new BasicPasswordTextBox
|
||||
otherTextBoxes.Add(new BasicTextBox
|
||||
{
|
||||
InputProperties = new TextInputProperties(TextInputType.Password),
|
||||
PlaceholderText = @"Password textbox",
|
||||
Text = "Secret ;)",
|
||||
Size = new Vector2(500, 30),
|
||||
@@ -169,12 +178,13 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestNumbersOnly()
|
||||
{
|
||||
NumberTextBox numbers = null;
|
||||
BasicTextBox numbers = null;
|
||||
|
||||
AddStep("add number textbox", () =>
|
||||
{
|
||||
textBoxes.Add(numbers = new NumberTextBox
|
||||
textBoxes.Add(numbers = new BasicTextBox
|
||||
{
|
||||
InputProperties = new TextInputProperties(TextInputType.Number),
|
||||
PlaceholderText = @"Only numbers",
|
||||
Size = new Vector2(500, 30),
|
||||
TabbableContentContainer = textBoxes
|
||||
@@ -800,6 +810,40 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
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()
|
||||
{
|
||||
@@ -1042,13 +1086,6 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
public new void InsertString(string text) => base.InsertString(text);
|
||||
}
|
||||
|
||||
private partial class NumberTextBox : BasicTextBox
|
||||
{
|
||||
protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character);
|
||||
|
||||
protected override bool AllowIme => false;
|
||||
}
|
||||
|
||||
private partial class CustomTextBox : BasicTextBox
|
||||
{
|
||||
protected override Drawable GetDrawableCharacter(char c) => new ScalingText(c, FontSize);
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
|
||||
AddAssert("text input not deactivated", () => textInput.DeactivationQueue.Count == 0);
|
||||
AddAssert("text input not activated again", () => textInput.ActivationQueue.Count == 0);
|
||||
AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() && textInput.EnsureActivatedQueue.Count == 0);
|
||||
AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() != default && textInput.EnsureActivatedQueue.Count == 0);
|
||||
|
||||
AddStep("click deselection", () =>
|
||||
{
|
||||
@@ -217,7 +217,7 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
|
||||
AddAssert("text input not deactivated", () => textInput.DeactivationQueue.Count == 0);
|
||||
AddAssert("text input not activated again", () => textInput.ActivationQueue.Count == 0);
|
||||
AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() && textInput.EnsureActivatedQueue.Count == 0);
|
||||
AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() != default && textInput.EnsureActivatedQueue.Count == 0);
|
||||
|
||||
AddStep("click-drag selection", () =>
|
||||
{
|
||||
@@ -500,7 +500,7 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
|
||||
AddStep("add second textbox", () => textInputContainer.Add(secondTextBox = new EventQueuesTextBox
|
||||
{
|
||||
ImeAllowed = allowIme,
|
||||
InputProperties = new TextInputProperties(TextInputType.Text, allowIme),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
CommitOnFocusLost = true,
|
||||
@@ -517,7 +517,7 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
|
||||
AddAssert("text input not deactivated", () => textInput.DeactivationQueue.Count == 0);
|
||||
AddAssert("text input not activated again", () => textInput.ActivationQueue.Count == 0);
|
||||
AddAssert($"text input ensure activated {(allowIme ? "with" : "without")} IME", () => textInput.EnsureActivatedQueue.Dequeue() == allowIme && textInput.EnsureActivatedQueue.Count == 0);
|
||||
AddAssert($"text input ensure activated {(allowIme ? "with" : "without")} IME", () => textInput.EnsureActivatedQueue.Dequeue().AllowIme == allowIme && textInput.EnsureActivatedQueue.Count == 0);
|
||||
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("text input deactivated", () => textInput.DeactivationQueue.Dequeue());
|
||||
@@ -574,10 +574,6 @@ namespace osu.Framework.Tests.Visual.UserInterface
|
||||
|
||||
public partial class EventQueuesTextBox : TestSceneTextBox.InsertableTextBox
|
||||
{
|
||||
public bool ImeAllowed { get; set; } = true;
|
||||
|
||||
protected override bool AllowIme => ImeAllowed;
|
||||
|
||||
public readonly Queue<bool> InputErrorQueue = new Queue<bool>();
|
||||
public readonly Queue<string> UserConsumedTextQueue = new Queue<string>();
|
||||
public readonly Queue<string> UserRemovedTextQueue = new Queue<string>();
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Framework.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneTextInputProperties : FrameworkTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("Create text boxes", () =>
|
||||
{
|
||||
FillFlowContainer flow;
|
||||
Child = new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = flow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 0.9f,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Spacing = new Vector2(20, 13),
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var textInputType in Enum.GetValues<TextInputType>())
|
||||
{
|
||||
flow.Add(new BasicTextBox
|
||||
{
|
||||
TabbableContentContainer = flow,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Width = 0.45f,
|
||||
PlaceholderText = $"{textInputType} (allow IME)",
|
||||
InputProperties = new TextInputProperties
|
||||
{
|
||||
Type = textInputType,
|
||||
AllowIme = true
|
||||
},
|
||||
});
|
||||
flow.Add(new BasicTextBox
|
||||
{
|
||||
TabbableContentContainer = flow,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Width = 0.45f,
|
||||
PlaceholderText = $"{textInputType} (no IME)",
|
||||
InputProperties = new TextInputProperties
|
||||
{
|
||||
Type = textInputType,
|
||||
AllowIme = false
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -26,81 +20,21 @@ namespace osu.Framework.iOS.Graphics.Textures
|
||||
|
||||
protected override unsafe Image<TPixel> ImageFromStream<TPixel>(Stream stream)
|
||||
{
|
||||
using (var nativeData = NSData.FromStream(stream))
|
||||
using (new NSAutoreleasePool())
|
||||
{
|
||||
if (nativeData == null)
|
||||
int length = (int)(stream.Length - stream.Position);
|
||||
var nativeData = NSMutableData.FromLength(length);
|
||||
|
||||
var bytesSpan = new Span<byte>(nativeData.MutableBytes.ToPointer(), length);
|
||||
stream.ReadExactly(bytesSpan);
|
||||
|
||||
using var uiImage = UIImage.LoadFromData(nativeData);
|
||||
if (uiImage == 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);
|
||||
|
||||
var image = new Image<TPixel>(width, height);
|
||||
byte* data = (byte*)accelerateImage.Data;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using UniformTypeIdentifiers;
|
||||
@@ -26,16 +27,24 @@ namespace osu.Framework.iOS
|
||||
if (documentInteraction.PresentPreview(true))
|
||||
return true;
|
||||
|
||||
// Since file menus on iPadOS appear in a popover-like style, UIDocumentInteractionController requires specifying
|
||||
// a rectangle in the present functions to display the menu as a popover around that rectangle.
|
||||
// Ultimately, we want to be given a rectangle by the game so the popover doesn't look out of place,
|
||||
// but for the time being, specify CGRectZero to make the popover display at the top left.
|
||||
var gameView = window.ViewController.View!;
|
||||
return documentInteraction.PresentOpenInMenu(gameView.Bounds, gameView, true);
|
||||
return documentInteraction.PresentOpenInMenu(CGRect.Empty, gameView, true);
|
||||
}
|
||||
|
||||
public bool PresentFile(string filename)
|
||||
{
|
||||
setupViewController(filename);
|
||||
|
||||
// Since file menus on iPadOS appear in a popover-like style, UIDocumentInteractionController requires specifying
|
||||
// a rectangle in the present functions to display the menu as a popover around that rectangle.
|
||||
// Ultimately, we want to be given a rectangle by the game so the popover doesn't look out of place,
|
||||
// but for the time being, specify CGRectZero to make the popover display at the top left.
|
||||
var gameView = window.ViewController.View!;
|
||||
return documentInteraction.PresentOptionsMenu(gameView.Bounds, gameView, true);
|
||||
return documentInteraction.PresentOptionsMenu(CGRect.Empty, gameView, true);
|
||||
}
|
||||
|
||||
private void setupViewController(string filename)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Foundation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions;
|
||||
@@ -11,6 +12,8 @@ using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Graphics.Video;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Input.Handlers.Mouse;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.iOS.Graphics.Textures;
|
||||
using osu.Framework.iOS.Graphics.Video;
|
||||
@@ -85,6 +88,20 @@ namespace osu.Framework.iOS
|
||||
public override VideoDecoder CreateVideoDecoder(Stream stream)
|
||||
=> new IOSVideoDecoder(Renderer, stream);
|
||||
|
||||
protected override IEnumerable<InputHandler> CreateAvailableInputHandlers()
|
||||
{
|
||||
var handlers = base.CreateAvailableInputHandlers();
|
||||
|
||||
foreach (var h in handlers.OfType<MouseHandler>())
|
||||
{
|
||||
// Similar to macOS, "relative mode" is also broken on iOS.
|
||||
h.UseRelativeMode.Value = false;
|
||||
h.UseRelativeMode.Default = false;
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public override ISystemFileSelector? CreateSystemFileSelector(string[] allowedExtensions)
|
||||
{
|
||||
IOSFileSelector? selector = null;
|
||||
|
||||
@@ -128,6 +128,20 @@ namespace osu.Framework
|
||||
|
||||
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>
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// <summary>
|
||||
/// The current scroll position.
|
||||
/// </summary>
|
||||
public float Current { get; private set; }
|
||||
public double Current { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The target scroll position which is exponentially approached by current via a rate of distance decay.
|
||||
@@ -126,12 +126,12 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// <remarks>
|
||||
/// When not animating scroll position, this will always be equal to <see cref="Current"/>.
|
||||
/// </remarks>
|
||||
public float Target { get; private set; }
|
||||
public double Target { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum distance that can be scrolled in the scroll direction.
|
||||
/// </summary>
|
||||
public float ScrollableExtent => Math.Max(AvailableContent - DisplayableContent, 0);
|
||||
public double ScrollableExtent => Math.Max(AvailableContent - DisplayableContent, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum distance that the scrollbar can move in the scroll direction.
|
||||
@@ -139,14 +139,14 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// <remarks>
|
||||
/// May not be accurate to actual display of scrollbar if <see cref="ToScrollbarPosition"/> or <see cref="FromScrollbarPosition"/> are overridden.
|
||||
/// </remarks>
|
||||
protected float ScrollbarMovementExtent => Math.Max(DisplayableContent - Scrollbar.DrawSize[ScrollDim], 0);
|
||||
protected double ScrollbarMovementExtent => Math.Max(DisplayableContent - Scrollbar.DrawSize[ScrollDim], 0);
|
||||
|
||||
/// <summary>
|
||||
/// Clamp a value to the available scroll range.
|
||||
/// </summary>
|
||||
/// <param name="position">The value to clamp.</param>
|
||||
/// <param name="extension">An extension value beyond the normal extent.</param>
|
||||
protected float Clamp(float position, float extension = 0) => Math.Max(Math.Min(position, ScrollableExtent + extension), -extension);
|
||||
protected double Clamp(double position, double extension = 0) => Math.Max(Math.Min(position, ScrollableExtent + extension), -extension);
|
||||
|
||||
protected override Container<T> Content => ScrollContent;
|
||||
|
||||
@@ -345,8 +345,8 @@ namespace osu.Framework.Graphics.Containers
|
||||
|
||||
Vector2 childDelta = ToLocalSpace(e.ScreenSpaceMousePosition) - ToLocalSpace(e.ScreenSpaceLastMousePosition);
|
||||
|
||||
float scrollOffset = -childDelta[ScrollDim];
|
||||
float clampedScrollOffset = Clamp(Target + scrollOffset) - Clamp(Target);
|
||||
double scrollOffset = -childDelta[ScrollDim];
|
||||
double clampedScrollOffset = Clamp(Target + scrollOffset) - Clamp(Target);
|
||||
|
||||
// If we are dragging past the extent of the scrollable area, half the offset
|
||||
// such that the user can feel it.
|
||||
@@ -418,13 +418,13 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// Immediately offsets the current and target scroll position.
|
||||
/// </summary>
|
||||
/// <param name="offset">The scroll offset.</param>
|
||||
public void OffsetScrollPosition(float offset)
|
||||
public virtual void OffsetScrollPosition(double offset)
|
||||
{
|
||||
Target += offset;
|
||||
Current += offset;
|
||||
}
|
||||
|
||||
private void scrollByOffset(float value, bool animated, double distanceDecay = float.PositiveInfinity) =>
|
||||
private void scrollByOffset(double value, bool animated, double distanceDecay = float.PositiveInfinity) =>
|
||||
OnUserScroll(Target + value, animated, distanceDecay);
|
||||
|
||||
/// <summary>
|
||||
@@ -454,7 +454,7 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// </summary>
|
||||
/// <param name="offset">The amount by which we should scroll.</param>
|
||||
/// <param name="animated">Whether to animate the movement.</param>
|
||||
public void ScrollBy(float offset, bool animated = true) => scrollTo(Target + offset, animated);
|
||||
public void ScrollBy(double offset, bool animated = true) => scrollTo(Target + offset, animated);
|
||||
|
||||
/// <summary>
|
||||
/// Handle a scroll to an absolute position from a user input.
|
||||
@@ -462,7 +462,7 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// <param name="value">The position to scroll to.</param>
|
||||
/// <param name="animated">Whether to animate the movement.</param>
|
||||
/// <param name="distanceDecay">Controls the rate with which the target position is approached after jumping to a specific location. Default is <see cref="DistanceDecayJump"/>.</param>
|
||||
protected virtual void OnUserScroll(float value, bool animated = true, double? distanceDecay = null) =>
|
||||
protected virtual void OnUserScroll(double value, bool animated = true, double? distanceDecay = null) =>
|
||||
ScrollTo(value, animated, distanceDecay);
|
||||
|
||||
/// <summary>
|
||||
@@ -471,9 +471,9 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// <param name="value">The position to scroll to.</param>
|
||||
/// <param name="animated">Whether to animate the movement.</param>
|
||||
/// <param name="distanceDecay">Controls the rate with which the target position is approached after jumping to a specific location. Default is <see cref="DistanceDecayJump"/>.</param>
|
||||
public void ScrollTo(float value, bool animated = true, double? distanceDecay = null) => scrollTo(value, animated, distanceDecay ?? DistanceDecayJump);
|
||||
public void ScrollTo(double value, bool animated = true, double? distanceDecay = null) => scrollTo(value, animated, distanceDecay ?? DistanceDecayJump);
|
||||
|
||||
private void scrollTo(float value, bool animated, double distanceDecay = float.PositiveInfinity)
|
||||
private void scrollTo(double value, bool animated, double distanceDecay = double.PositiveInfinity)
|
||||
{
|
||||
Target = Clamp(value, ClampExtension);
|
||||
|
||||
@@ -497,11 +497,11 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// <param name="animated">Whether to animate the movement.</param>
|
||||
public void ScrollIntoView(Drawable d, bool animated = true)
|
||||
{
|
||||
float childPos0 = GetChildPosInContent(d);
|
||||
float childPos1 = GetChildPosInContent(d, d.DrawSize);
|
||||
double childPos0 = GetChildPosInContent(d);
|
||||
double childPos1 = GetChildPosInContent(d, d.DrawSize);
|
||||
|
||||
float minPos = Math.Min(childPos0, childPos1);
|
||||
float maxPos = Math.Max(childPos0, childPos1);
|
||||
double minPos = Math.Min(childPos0, childPos1);
|
||||
double maxPos = Math.Max(childPos0, childPos1);
|
||||
|
||||
if (minPos < Current || (minPos > Current && d.DrawSize[ScrollDim] > DisplayableContent))
|
||||
ScrollTo(minPos, animated);
|
||||
@@ -515,14 +515,14 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// <param name="d">The child to get the position from.</param>
|
||||
/// <param name="offset">Positional offset in the child's space.</param>
|
||||
/// <returns>The position of the child.</returns>
|
||||
public float GetChildPosInContent(Drawable d, Vector2 offset) => d.ToSpaceOfOtherDrawable(offset, ScrollContent)[ScrollDim];
|
||||
public virtual double GetChildPosInContent(Drawable d, Vector2 offset) => d.ToSpaceOfOtherDrawable(offset, ScrollContent)[ScrollDim];
|
||||
|
||||
/// <summary>
|
||||
/// Determines the position of a child in the content.
|
||||
/// </summary>
|
||||
/// <param name="d">The child to get the position from.</param>
|
||||
/// <returns>The position of the child.</returns>
|
||||
public float GetChildPosInContent(Drawable d) => GetChildPosInContent(d, Vector2.Zero);
|
||||
public double GetChildPosInContent(Drawable d) => GetChildPosInContent(d, Vector2.Zero);
|
||||
|
||||
private void updatePosition()
|
||||
{
|
||||
@@ -544,15 +544,15 @@ namespace osu.Framework.Graphics.Containers
|
||||
localDistanceDecay = distance_decay_clamping * 2;
|
||||
|
||||
// Lastly, we gradually nudge the target towards valid bounds.
|
||||
Target = (float)Interpolation.Lerp(Clamp(Target), Target, Math.Exp(-distance_decay_clamping * Time.Elapsed));
|
||||
Target = Interpolation.Lerp(Clamp(Target), Target, Math.Exp(-distance_decay_clamping * Time.Elapsed));
|
||||
|
||||
float clampedTarget = Clamp(Target);
|
||||
double clampedTarget = Clamp(Target);
|
||||
if (Precision.AlmostEquals(clampedTarget, Target))
|
||||
Target = clampedTarget;
|
||||
}
|
||||
|
||||
// Exponential interpolation between the target and our current scroll position.
|
||||
Current = (float)Interpolation.Lerp(Target, Current, Math.Exp(-localDistanceDecay * Time.Elapsed));
|
||||
Current = Interpolation.Lerp(Target, Current, Math.Exp(-localDistanceDecay * Time.Elapsed));
|
||||
|
||||
// This prevents us from entering the de-normalized range of floating point numbers when approaching target closely.
|
||||
if (Precision.AlmostEquals(Current, Target))
|
||||
@@ -578,15 +578,27 @@ namespace osu.Framework.Graphics.Containers
|
||||
}
|
||||
|
||||
if (ScrollDirection == Direction.Horizontal)
|
||||
{
|
||||
Scrollbar.X = ToScrollbarPosition(Current);
|
||||
ScrollContent.X = -Current + ScrollableExtent * ScrollContent.RelativeAnchorPosition.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
Scrollbar.Y = ToScrollbarPosition(Current);
|
||||
ScrollContent.Y = -Current + ScrollableExtent * ScrollContent.RelativeAnchorPosition.Y;
|
||||
}
|
||||
|
||||
ApplyCurrentToContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the final internal step of updating the scroll container, which takes
|
||||
/// <see cref="Current"/> and applies it to <see cref="ScrollContent"/> in order to
|
||||
/// correctly offset children.
|
||||
///
|
||||
/// Overriding this method can be used to inhibit this default behaviour, to for instance
|
||||
/// redirect the positioning to another container or change the way it is applied.
|
||||
/// </summary>
|
||||
protected virtual void ApplyCurrentToContent()
|
||||
{
|
||||
if (ScrollDirection == Direction.Horizontal)
|
||||
ScrollContent.X = (float)(-Current + (ScrollableExtent * ScrollContent.RelativeAnchorPosition.X));
|
||||
else
|
||||
ScrollContent.Y = (float)(-Current + (ScrollableExtent * ScrollContent.RelativeAnchorPosition.Y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -594,12 +606,12 @@ namespace osu.Framework.Graphics.Containers
|
||||
/// </summary>
|
||||
/// <param name="scrollPosition">The absolute scroll position (e.g. <see cref="Current"/>).</param>
|
||||
/// <returns>The scrollbar position.</returns>
|
||||
protected virtual float ToScrollbarPosition(float scrollPosition)
|
||||
protected virtual float ToScrollbarPosition(double scrollPosition)
|
||||
{
|
||||
if (Precision.AlmostEquals(0, ScrollableExtent))
|
||||
return 0;
|
||||
|
||||
return ScrollbarMovementExtent * (scrollPosition / ScrollableExtent);
|
||||
return (float)(ScrollbarMovementExtent * (scrollPosition / ScrollableExtent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -612,7 +624,7 @@ namespace osu.Framework.Graphics.Containers
|
||||
if (Precision.AlmostEquals(0, ScrollbarMovementExtent))
|
||||
return 0;
|
||||
|
||||
return ScrollableExtent * (scrollbarPosition / ScrollbarMovementExtent);
|
||||
return (float)(ScrollableExtent * (scrollbarPosition / ScrollbarMovementExtent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace osu.Framework.Graphics.Containers
|
||||
|
||||
private void performFilter()
|
||||
{
|
||||
string[] terms = (searchTerm ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
string[] terms = (searchTerm ?? string.Empty).Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
matchSubTree(this, terms, terms.Length > 0, allowNonContiguousMatching);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace osu.Framework.Graphics
|
||||
/// <param name="drawable">The <see cref="Drawable"/> to be checked.</param>
|
||||
/// <param name="root">The root to be checked against.</param>
|
||||
/// <returns>Whether the drawable was rooted.</returns>
|
||||
internal static bool IsRootedAt(this Drawable? drawable, Drawable root)
|
||||
public static bool IsRootedAt(this Drawable? drawable, Drawable root)
|
||||
{
|
||||
if (drawable == root) return true;
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// 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 osu.Framework.Input;
|
||||
|
||||
namespace osu.Framework.Graphics.UserInterface
|
||||
{
|
||||
public partial class BasicPasswordTextBox : BasicTextBox, ISuppressKeyEventLogging
|
||||
{
|
||||
protected virtual char MaskCharacter => '*';
|
||||
|
||||
protected override bool AllowClipboardExport => false;
|
||||
|
||||
protected override bool AllowWordNavigation => false;
|
||||
|
||||
protected override bool AllowIme => false;
|
||||
|
||||
protected override Drawable AddCharacterToFlow(char c) => base.AddCharacterToFlow(MaskCharacter);
|
||||
}
|
||||
}
|
||||
@@ -192,20 +192,20 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
platformSource.OnImeResult += TriggerImeResult;
|
||||
}
|
||||
|
||||
protected override void ActivateTextInput(bool allowIme)
|
||||
protected override void ActivateTextInput(TextInputProperties properties)
|
||||
{
|
||||
base.ActivateTextInput(allowIme);
|
||||
base.ActivateTextInput(properties);
|
||||
|
||||
if (allowTextInput)
|
||||
platformSource.Activate(allowIme, imeRectangle ?? RectangleF.Empty);
|
||||
platformSource.Activate(properties, imeRectangle ?? RectangleF.Empty);
|
||||
}
|
||||
|
||||
protected override void EnsureTextInputActivated(bool allowIme)
|
||||
protected override void EnsureTextInputActivated(TextInputProperties properties)
|
||||
{
|
||||
base.EnsureTextInputActivated(allowIme);
|
||||
base.EnsureTextInputActivated(properties);
|
||||
|
||||
if (allowTextInput)
|
||||
platformSource.EnsureActivated(allowIme, imeRectangle);
|
||||
platformSource.EnsureActivated(properties, imeRectangle);
|
||||
}
|
||||
|
||||
protected override void DeactivateTextInput()
|
||||
|
||||
@@ -29,13 +29,18 @@ using osuTK.Input;
|
||||
|
||||
namespace osu.Framework.Graphics.UserInterface
|
||||
{
|
||||
public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<string>, IKeyBindingHandler<PlatformAction>
|
||||
public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<string>, IKeyBindingHandler<PlatformAction>, ICanSuppressKeyEventLogging
|
||||
{
|
||||
protected FillFlowContainer TextFlow { get; private set; }
|
||||
protected Container TextContainer { get; private set; }
|
||||
|
||||
public override bool HandleNonPositionalInput => HasFocus;
|
||||
|
||||
/// <summary>
|
||||
/// A character displayed whenever the type of text input set by <see cref="TextInputProperties.Type"/> is hidden.
|
||||
/// </summary>
|
||||
protected virtual char MaskCharacter => '*';
|
||||
|
||||
/// <summary>
|
||||
/// Padding to be used within the TextContainer. Requires special handling due to the sideways scrolling of text content.
|
||||
/// </summary>
|
||||
@@ -50,12 +55,14 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
/// <summary>
|
||||
/// Whether clipboard copying functionality is allowed.
|
||||
/// </summary>
|
||||
protected virtual bool AllowClipboardExport => true;
|
||||
protected virtual bool AllowClipboardExport => !InputProperties.Type.IsPassword();
|
||||
|
||||
/// <summary>
|
||||
/// Whether seeking to word boundaries is allowed.
|
||||
/// </summary>
|
||||
protected virtual bool AllowWordNavigation => true;
|
||||
protected virtual bool AllowWordNavigation => !InputProperties.Type.IsPassword();
|
||||
|
||||
bool ICanSuppressKeyEventLogging.SuppressKeyEventLogging => InputProperties.Type.IsPassword();
|
||||
|
||||
/// <summary>
|
||||
/// Represents the left/right selection coordinates of the word double clicked on when dragging.
|
||||
@@ -67,18 +74,14 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public virtual bool HandleLeftRightArrows => true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to allow IME input when this text box has input focus.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is just a hint to the native implementation, some might respect this,
|
||||
/// while others will ignore and always have the IME (dis)allowed.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// Useful for situations where IME input is not wanted, such as for passwords, numbers, or romanised text.
|
||||
/// </example>
|
||||
[Obsolete($"Use {nameof(InputProperties)} instead.")] // can be removed 20250506
|
||||
protected virtual bool AllowIme => true;
|
||||
|
||||
/// <summary>
|
||||
/// A set of properties to consider when interacting with this <see cref="TextBox"/>.
|
||||
/// </summary>
|
||||
public TextInputProperties InputProperties { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Check if a character can be added to this TextBox.
|
||||
/// </summary>
|
||||
@@ -87,9 +90,14 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
protected virtual bool CanAddCharacter(char character) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Private helper for <see cref="CanAddCharacter"/>, additionally requiring that the character is not a control character.
|
||||
/// Private helper for <see cref="CanAddCharacter"/>, additionally requiring that the character is not a control character and obeys <see cref="TextInputProperties.Type"/>.
|
||||
/// </summary>
|
||||
private bool canAddCharacter(char character) => !char.IsControl(character) && CanAddCharacter(character);
|
||||
private bool canAddCharacter(char character)
|
||||
{
|
||||
return !char.IsControl(character)
|
||||
&& (!InputProperties.Type.IsNumerical() || char.IsAsciiDigit(character))
|
||||
&& CanAddCharacter(character);
|
||||
}
|
||||
|
||||
private bool readOnly;
|
||||
|
||||
@@ -158,6 +166,10 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
|
||||
protected TextBox()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
InputProperties = new TextInputProperties(TextInputType.Text, AllowIme);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
Masking = true;
|
||||
|
||||
Children = new Drawable[]
|
||||
@@ -773,6 +785,7 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
TextFlow.ChangeChildDepth(TextFlow[i], getDepthForCharacterIndex(i));
|
||||
|
||||
selectionStart = selectionEnd = removeStart;
|
||||
doubleClickWord = null;
|
||||
|
||||
endTextChange(beganChange);
|
||||
cursorAndLayout.Invalidate();
|
||||
@@ -789,6 +802,9 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
|
||||
protected virtual Drawable AddCharacterToFlow(char c)
|
||||
{
|
||||
if (InputProperties.Type.IsPassword())
|
||||
c = MaskCharacter;
|
||||
|
||||
// Remove all characters to the right and store them in a local list,
|
||||
// such that their depth can be updated.
|
||||
List<Drawable> charsRight = new List<Drawable>();
|
||||
@@ -1339,7 +1355,7 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (!ReadOnly && textInputBound)
|
||||
textInput.EnsureActivated(AllowIme);
|
||||
textInput.EnsureActivated(InputProperties);
|
||||
|
||||
return !ReadOnly;
|
||||
}
|
||||
@@ -1366,7 +1382,7 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
|
||||
if (textInputBound)
|
||||
{
|
||||
textInput.EnsureActivated(AllowIme);
|
||||
textInput.EnsureActivated(InputProperties);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1374,9 +1390,9 @@ namespace osu.Framework.Graphics.UserInterface
|
||||
// We don't deactivate and activate, but instead keep text input active during the focus handoff, so that virtual keyboards on phones don't flicker.
|
||||
|
||||
if (previous?.textInput == textInput)
|
||||
textInput.EnsureActivated(AllowIme, ScreenSpaceDrawQuad.AABBFloat);
|
||||
textInput.EnsureActivated(InputProperties, ScreenSpaceDrawQuad.AABBFloat);
|
||||
else
|
||||
textInput.Activate(AllowIme, ScreenSpaceDrawQuad.AABBFloat);
|
||||
textInput.Activate(InputProperties, ScreenSpaceDrawQuad.AABBFloat);
|
||||
|
||||
textInput.OnTextInput += handleTextInput;
|
||||
textInput.OnImeComposition += handleImeComposition;
|
||||
|
||||
@@ -133,9 +133,18 @@ namespace osu.Framework.Input
|
||||
}
|
||||
|
||||
if (handledBy != null)
|
||||
Logger.Log($"{e} handled by {handledBy}.", LoggingTarget.Runtime, LogLevel.Debug);
|
||||
{
|
||||
Logger.Log(SuppressLoggingEventInformation(handledBy)
|
||||
? $"{e.GetType().Name} handled by {handledBy}."
|
||||
: $"{e} handled by {handledBy}.", LoggingTarget.Runtime, LogLevel.Debug);
|
||||
}
|
||||
|
||||
return handledBy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether information about the event should be suppressed from logging for the given drawable.
|
||||
/// </summary>
|
||||
protected virtual bool SuppressLoggingEventInformation(Drawable drawable) => false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
namespace osu.Framework.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface which suppresses logging of keyboard input events.
|
||||
/// An interface which suppresses logging of keyboard input events.
|
||||
/// Useful for password fields, where user input should not be logged.
|
||||
/// </summary>
|
||||
public interface ISuppressKeyEventLogging
|
||||
public interface ICanSuppressKeyEventLogging
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether key event logging should be suppressed for this drawable.
|
||||
/// </summary>
|
||||
bool SuppressKeyEventLogging { get; }
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ListExtensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
@@ -1002,37 +1001,13 @@ namespace osu.Framework.Input
|
||||
{
|
||||
foreach (var d in drawables)
|
||||
{
|
||||
if (!d.TriggerEvent(e)) continue;
|
||||
|
||||
if (shouldLog(e))
|
||||
{
|
||||
string detail = d is ISuppressKeyEventLogging ? e.GetType().ReadableName() : e.ToString();
|
||||
Logger.Log($"{detail} handled by {d}.", LoggingTarget.Runtime, LogLevel.Debug);
|
||||
}
|
||||
|
||||
return true;
|
||||
if (d.TriggerEvent(e))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool shouldLog(UIEvent eventType)
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case KeyDownEvent k:
|
||||
return !k.Repeat;
|
||||
|
||||
case DragEvent:
|
||||
case ScrollEvent:
|
||||
case MouseMoveEvent:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unfocus the current focused drawable if it is no longer in a valid state.
|
||||
/// </summary>
|
||||
|
||||
@@ -33,5 +33,7 @@ namespace osu.Framework.Input
|
||||
|
||||
protected override void HandleButtonUp(InputState state, List<Drawable> targets) =>
|
||||
PropagateButtonEvent(targets, new KeyUpEvent(state, Button));
|
||||
|
||||
protected override bool SuppressLoggingEventInformation(Drawable drawable) => drawable is ICanSuppressKeyEventLogging canSuppress && canSuppress.SuppressKeyEventLogging;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,16 +37,16 @@ namespace osu.Framework.Input
|
||||
TriggerImeComposition(text, selectionStart, selectionLength);
|
||||
}
|
||||
|
||||
protected override void ActivateTextInput(bool allowIme)
|
||||
protected override void ActivateTextInput(TextInputProperties properties)
|
||||
{
|
||||
window.TextInput += handleTextInput;
|
||||
window.TextEditing += handleTextEditing;
|
||||
window.StartTextInput(allowIme);
|
||||
window.StartTextInput(properties);
|
||||
}
|
||||
|
||||
protected override void EnsureTextInputActivated(bool allowIme)
|
||||
protected override void EnsureTextInputActivated(TextInputProperties properties)
|
||||
{
|
||||
window.StartTextInput(allowIme);
|
||||
window.StartTextInput(properties);
|
||||
}
|
||||
|
||||
protected override void DeactivateTextInput()
|
||||
|
||||
21
osu.Framework/Input/TextInputProperties.cs
Normal file
21
osu.Framework/Input/TextInputProperties.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Framework.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a number of properties to consider during a text input session.
|
||||
/// </summary>
|
||||
/// <param name="Type">The type of text being input.</param>
|
||||
/// <param name="AllowIme">
|
||||
/// <para>
|
||||
/// Whether IME should be allowed during this text input session, if supported by the given text input type.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Note that this is just a hint to the native implementation, some might respect this,
|
||||
/// while others will ignore and always have the IME (dis)allowed.
|
||||
/// </para>
|
||||
/// </param>
|
||||
/// <param name="AutoCapitalisation">Whether text should be automatically capitalised.</param>
|
||||
public record struct TextInputProperties(TextInputType Type, bool AllowIme = true, bool AutoCapitalisation = false);
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
@@ -29,7 +27,7 @@ namespace osu.Framework.Input
|
||||
/// Activates this <see cref="TextInputSource"/>.
|
||||
/// User text input can be acquired through <see cref="OnTextInput"/>, <see cref="OnImeComposition"/> and <see cref="OnImeResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="allowIme">Whether input using IME should be allowed.</param>
|
||||
/// <param name="properties">A set of properties to consider during this text input session.</param>
|
||||
/// <param name="imeRectangle">
|
||||
/// Rough location of where the text will be input, so the native implementation
|
||||
/// can adjust virtual keyboards and IME popups.
|
||||
@@ -37,36 +35,36 @@ namespace osu.Framework.Input
|
||||
/// <remarks>
|
||||
/// Each <see cref="Activate"/> must be followed by a <see cref="Deactivate"/>.
|
||||
/// </remarks>
|
||||
public void Activate(bool allowIme, RectangleF imeRectangle)
|
||||
public void Activate(TextInputProperties properties, RectangleF imeRectangle)
|
||||
{
|
||||
if (Interlocked.Increment(ref activationCounter) == 1)
|
||||
{
|
||||
SetImeRectangle(imeRectangle);
|
||||
ActivateTextInput(allowIme);
|
||||
ActivateTextInput(properties);
|
||||
}
|
||||
else
|
||||
// the latest consumer that activated should always take precedence in (dis)allowing IME.
|
||||
EnsureActivated(allowIme, imeRectangle);
|
||||
EnsureActivated(properties, imeRectangle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the native implementation that retrieves user text input is activated
|
||||
/// and that the user can start entering text.
|
||||
/// </summary>
|
||||
/// <param name="allowIme">Whether input using IME should be allowed.</param>
|
||||
/// <param name="properties">A set of properties to consider during this text input session.</param>
|
||||
/// <param name="imeRectangle">
|
||||
/// Rough location of where the text will be input, so the native implementation
|
||||
/// can adjust virtual keyboards and IME popups. Can be <c>null</c> to avoid changing
|
||||
/// the IME rectangle.
|
||||
/// </param>
|
||||
public void EnsureActivated(bool allowIme, RectangleF? imeRectangle = null)
|
||||
public void EnsureActivated(TextInputProperties properties, RectangleF? imeRectangle = null)
|
||||
{
|
||||
if (activationCounter >= 1)
|
||||
{
|
||||
if (imeRectangle.HasValue)
|
||||
SetImeRectangle(imeRectangle.Value);
|
||||
|
||||
EnsureTextInputActivated(allowIme);
|
||||
EnsureTextInputActivated(properties);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,29 +101,29 @@ namespace osu.Framework.Input
|
||||
/// <summary>
|
||||
/// Invoked on text input.
|
||||
/// </summary>
|
||||
public event Action<string> OnTextInput;
|
||||
public event Action<string>? OnTextInput;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when IME composition starts or changes.
|
||||
/// </summary>
|
||||
/// <remarks>Empty string for text means that the composition has been cancelled.</remarks>
|
||||
public event ImeCompositionDelegate OnImeComposition;
|
||||
public event ImeCompositionDelegate? OnImeComposition;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when IME composition successfully completes.
|
||||
/// </summary>
|
||||
public event Action<string> OnImeResult;
|
||||
public event Action<string>? OnImeResult;
|
||||
|
||||
/// <summary>
|
||||
/// Activates the native implementation that provides text input.
|
||||
/// Should be overriden per-platform.
|
||||
/// </summary>
|
||||
/// <param name="allowIme">Whether input using IME should be allowed.</param>
|
||||
/// <param name="properties">A set of properties to consider during this text input session.</param>
|
||||
/// <remarks>
|
||||
/// An active native implementation should call <see cref="TriggerTextInput"/> on new text input
|
||||
/// and forward IME composition events through <see cref="TriggerImeComposition"/> and <see cref="TriggerImeResult"/>.
|
||||
/// </remarks>
|
||||
protected virtual void ActivateTextInput(bool allowIme)
|
||||
protected virtual void ActivateTextInput(TextInputProperties properties)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -134,7 +132,7 @@ namespace osu.Framework.Input
|
||||
/// <remarks>
|
||||
/// Only called if the native implementation has been activated with <see cref="Activate"/>.
|
||||
/// </remarks>
|
||||
protected virtual void EnsureTextInputActivated(bool allowIme)
|
||||
protected virtual void EnsureTextInputActivated(TextInputProperties properties)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
74
osu.Framework/Input/TextInputType.cs
Normal file
74
osu.Framework/Input/TextInputType.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Framework.Input
|
||||
{
|
||||
public enum TextInputType
|
||||
{
|
||||
/// <summary>
|
||||
/// Plain text, default type of text input.
|
||||
/// </summary>
|
||||
Text,
|
||||
|
||||
/// <summary>
|
||||
/// The text input is a person's name.
|
||||
/// </summary>
|
||||
Name,
|
||||
|
||||
/// <summary>
|
||||
/// The text input is an email address.
|
||||
/// </summary>
|
||||
EmailAddress,
|
||||
|
||||
/// <summary>
|
||||
/// The text input is a username.
|
||||
/// </summary>
|
||||
Username,
|
||||
|
||||
/// <summary>
|
||||
/// The text input is numerical.
|
||||
/// </summary>
|
||||
Number,
|
||||
|
||||
/// <summary>
|
||||
/// The text input is a password hidden from the user.
|
||||
/// </summary>
|
||||
Password,
|
||||
|
||||
/// <summary>
|
||||
/// The text input is a numerical password hidden from the user.
|
||||
/// </summary>
|
||||
NumericalPassword,
|
||||
}
|
||||
|
||||
public static class TextInputTypeExtensions
|
||||
{
|
||||
public static bool IsPassword(this TextInputType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TextInputType.Password:
|
||||
case TextInputType.NumericalPassword:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsNumerical(this TextInputType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TextInputType.Number:
|
||||
case TextInputType.NumericalPassword:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool SupportsIme(this TextInputType type) => type == TextInputType.Name || type == TextInputType.Text;
|
||||
}
|
||||
}
|
||||
69
osu.Framework/Platform/Apple/AppleTextureLoaderStore.cs
Normal file
69
osu.Framework/Platform/Apple/AppleTextureLoaderStore.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform.Apple.Native;
|
||||
using osu.Framework.Platform.Apple.Native.Accelerate;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Advanced;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace osu.Framework.Platform.Apple
|
||||
{
|
||||
internal abstract class AppleTextureLoaderStore : TextureLoaderStore
|
||||
{
|
||||
protected AppleTextureLoaderStore(IResourceStore<byte[]> store)
|
||||
: base(store)
|
||||
{
|
||||
}
|
||||
|
||||
protected unsafe Image<TPixel> ImageFromCGImage<TPixel>(CGImage cgImage)
|
||||
where TPixel : unmanaged, IPixel<TPixel>
|
||||
{
|
||||
int width = (int)cgImage.Width;
|
||||
int height = (int)cgImage.Height;
|
||||
|
||||
var format = new vImage_CGImageFormat
|
||||
{
|
||||
BitsPerComponent = 8,
|
||||
BitsPerPixel = 32,
|
||||
ColorSpace = CGColorSpace.CreateDeviceRGB(),
|
||||
// notably, macOS & iOS generally use premultiplied alpha when rendering image to pixels via CGBitmapContext or otherwise,
|
||||
// but vImage offers rendering as straight alpha by specifying Last instead of PremultipliedLast.
|
||||
BitmapInfo = (CGBitmapInfo)CGImageAlphaInfo.Last,
|
||||
Decode = null,
|
||||
RenderingIntent = CGColorRenderingIntent.Default,
|
||||
};
|
||||
|
||||
vImage_Buffer accImage = default;
|
||||
|
||||
// perform initial call to retrieve preferred alignment and bytes-per-row values for the given image dimensions.
|
||||
nuint alignment = (nuint)vImage.Init(&accImage, (uint)height, (uint)width, 32, vImage_Flags.NoAllocate);
|
||||
Debug.Assert(alignment > 0);
|
||||
|
||||
// allocate aligned memory region to contain image pixel data.
|
||||
nuint bytesPerRow = accImage.BytesPerRow;
|
||||
nuint bytesCount = bytesPerRow * accImage.Height;
|
||||
accImage.Data = (byte*)NativeMemory.AlignedAlloc(bytesCount, alignment);
|
||||
|
||||
var result = vImage.InitWithCGImage(&accImage, &format, null, cgImage.Handle, vImage_Flags.NoAllocate);
|
||||
Debug.Assert(result == vImage_Error.NoError);
|
||||
|
||||
var image = new Image<TPixel>(width, height);
|
||||
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
var imageRow = image.DangerousGetPixelRowMemory(i);
|
||||
var dataRow = new ReadOnlySpan<TPixel>(&accImage.Data[(int)bytesPerRow * i], width);
|
||||
dataRow.CopyTo(imageRow.Span);
|
||||
}
|
||||
|
||||
NativeMemory.AlignedFree(accImage.Data);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
osu.Framework/Platform/Apple/Native/Accelerate/vImage.cs
Normal file
19
osu.Framework/Platform/Apple/Native/Accelerate/vImage.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native.Accelerate
|
||||
{
|
||||
internal static unsafe partial class vImage
|
||||
{
|
||||
[LibraryImport(Interop.LIB_ACCELERATE, EntryPoint = "vImageBuffer_Init")]
|
||||
internal static partial vImage_Error Init(vImage_Buffer* buf, uint height, uint width, uint pixelBits, vImage_Flags flags);
|
||||
|
||||
[LibraryImport(Interop.LIB_ACCELERATE, EntryPoint = "vImageBuffer_InitWithCGImage")]
|
||||
internal static partial vImage_Error InitWithCGImage(vImage_Buffer* buf, vImage_CGImageFormat* format, double* backgroundColour, IntPtr image, vImage_Flags flags);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native.Accelerate
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe struct vImage_Buffer
|
||||
{
|
||||
public byte* Data;
|
||||
public nuint Height;
|
||||
public nuint Width;
|
||||
public nuint BytesPerRow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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.
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native.Accelerate
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe struct vImage_CGImageFormat
|
||||
{
|
||||
public uint BitsPerComponent;
|
||||
public uint BitsPerPixel;
|
||||
public CGColorSpace ColorSpace;
|
||||
public CGBitmapInfo BitmapInfo;
|
||||
public uint Version;
|
||||
public double* Decode;
|
||||
public CGColorRenderingIntent RenderingIntent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native.Accelerate
|
||||
{
|
||||
internal enum vImage_Error : long
|
||||
{
|
||||
OutOfPlaceOperationRequired = -21780,
|
||||
ColorSyncIsAbsent = -21779,
|
||||
InvalidImageFormat = -21778,
|
||||
InvalidRowBytes = -21777,
|
||||
InternalError = -21776,
|
||||
UnknownFlagsBit = -21775,
|
||||
BufferSizeMismatch = -21774,
|
||||
InvalidParameter = -21773,
|
||||
NullPointerArgument = -21772,
|
||||
MemoryAllocationError = -21771,
|
||||
InvalidOffsetY = -21770,
|
||||
InvalidOffsetX = -21769,
|
||||
InvalidEdgeStyle = -21768,
|
||||
InvalidKernelSize = -21767,
|
||||
RoiLargerThanInputBuffer = -21766,
|
||||
NoError = 0,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native.Accelerate
|
||||
{
|
||||
internal enum vImage_Flags : uint
|
||||
{
|
||||
NoFlags = 0,
|
||||
LeaveAlphaUnchanged = 1,
|
||||
CopyInPlace = 2,
|
||||
BackgroundColorFill = 4,
|
||||
EdgeExtend = 8,
|
||||
DoNotTile = 16,
|
||||
HighQualityResampling = 32,
|
||||
TruncateKernel = 64,
|
||||
GetTempBufferSize = 128,
|
||||
PrintDiagnosticsToConsole = 256,
|
||||
NoAllocate = 512,
|
||||
}
|
||||
}
|
||||
24
osu.Framework/Platform/Apple/Native/CGBitmapContext.cs
Normal file
24
osu.Framework/Platform/Apple/Native/CGBitmapContext.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
internal readonly partial struct CGBitmapContext
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
|
||||
internal CGBitmapContext(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
[LibraryImport(Interop.LIB_CORE_GRAPHICS, EntryPoint = "CGBitmapContextCreate")]
|
||||
public static partial CGBitmapContext Create(IntPtr data, nuint width, nuint height, nuint bitsPerComponent, nuint bytesPerRow, CGColorSpace colorSpace, CGBitmapInfo bitmapInfo);
|
||||
|
||||
[LibraryImport(Interop.LIB_CORE_GRAPHICS, EntryPoint = "CGContextDrawImage")]
|
||||
public static partial void DrawImage(CGBitmapContext context, CGRect rect, CGImage image);
|
||||
}
|
||||
}
|
||||
26
osu.Framework/Platform/Apple/Native/CGBitmapInfo.cs
Normal file
26
osu.Framework/Platform/Apple/Native/CGBitmapInfo.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
public enum CGBitmapInfo : uint
|
||||
{
|
||||
None,
|
||||
PremultipliedLast,
|
||||
PremultipliedFirst,
|
||||
Last,
|
||||
First,
|
||||
NoneSkipLast,
|
||||
NoneSkipFirst,
|
||||
Only,
|
||||
AlphaInfoMask = 31,
|
||||
FloatInfoMask = 3840,
|
||||
FloatComponents = 256,
|
||||
ByteOrderMask = 28672,
|
||||
ByteOrderDefault = 0,
|
||||
ByteOrder16Little = 4096,
|
||||
ByteOrder32Little = 8192,
|
||||
ByteOrder16Big = 12288,
|
||||
ByteOrder32Big = 16384,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
public enum CGColorRenderingIntent
|
||||
{
|
||||
Default,
|
||||
AbsoluteColorimetric,
|
||||
RelativeColorimetric,
|
||||
Perceptual,
|
||||
Saturation,
|
||||
}
|
||||
}
|
||||
21
osu.Framework/Platform/Apple/Native/CGColorSpace.cs
Normal file
21
osu.Framework/Platform/Apple/Native/CGColorSpace.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
public readonly partial struct CGColorSpace
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
|
||||
internal CGColorSpace(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
[LibraryImport(Interop.LIB_CORE_GRAPHICS, EntryPoint = "CGColorSpaceCreateDeviceRGB")]
|
||||
internal static partial CGColorSpace CreateDeviceRGB();
|
||||
}
|
||||
}
|
||||
33
osu.Framework/Platform/Apple/Native/CGImage.cs
Normal file
33
osu.Framework/Platform/Apple/Native/CGImage.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
internal readonly partial struct CGImage
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
|
||||
public CGImage(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
internal nuint Width => GetWidth(this);
|
||||
internal nuint Height => GetHeight(this);
|
||||
|
||||
[LibraryImport(Interop.LIB_CORE_GRAPHICS, EntryPoint = "CGImageGetWidth")]
|
||||
internal static partial nuint GetWidth(CGImage image);
|
||||
|
||||
[LibraryImport(Interop.LIB_CORE_GRAPHICS, EntryPoint = "CGImageGetHeight")]
|
||||
internal static partial nuint GetHeight(CGImage image);
|
||||
|
||||
[LibraryImport(Interop.LIB_CORE_GRAPHICS, EntryPoint = "CGImageRelease")]
|
||||
internal static partial void Release(CGImage image);
|
||||
|
||||
[LibraryImport(Interop.LIB_CORE_FOUNDATION, EntryPoint = "CFGetRetainCount")]
|
||||
internal static partial int GetRetainCount(CGImage image);
|
||||
}
|
||||
}
|
||||
17
osu.Framework/Platform/Apple/Native/CGImageAlphaInfo.cs
Normal file
17
osu.Framework/Platform/Apple/Native/CGImageAlphaInfo.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
internal enum CGImageAlphaInfo : uint
|
||||
{
|
||||
None,
|
||||
PremultipliedLast,
|
||||
PremultipliedFirst,
|
||||
Last,
|
||||
First,
|
||||
NoneSkipLast,
|
||||
NoneSkipFirst,
|
||||
Only,
|
||||
}
|
||||
}
|
||||
14
osu.Framework/Platform/Apple/Native/CGPoint.cs
Normal file
14
osu.Framework/Platform/Apple/Native/CGPoint.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CGPoint
|
||||
{
|
||||
internal double X;
|
||||
internal double Y;
|
||||
}
|
||||
}
|
||||
14
osu.Framework/Platform/Apple/Native/CGRect.cs
Normal file
14
osu.Framework/Platform/Apple/Native/CGRect.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CGRect
|
||||
{
|
||||
internal CGPoint Origin;
|
||||
internal CGSize Size;
|
||||
}
|
||||
}
|
||||
14
osu.Framework/Platform/Apple/Native/CGSize.cs
Normal file
14
osu.Framework/Platform/Apple/Native/CGSize.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CGSize
|
||||
{
|
||||
internal double Width;
|
||||
internal double Height;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Platform.MacOS.Native;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace osu.Framework.Platform.Apple.Native
|
||||
internal const string LIB_OBJ_C = "/usr/lib/libobjc.dylib";
|
||||
internal const string LIB_CORE_GRAPHICS = "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics";
|
||||
internal const string LIB_ACCELERATE = "/System/Library/Frameworks/Accelerate.framework/Accelerate";
|
||||
internal const string LIB_CORE_FOUNDATION = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
|
||||
|
||||
internal const int RTLD_NOW = 2;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Platform.MacOS.Native;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
|
||||
37
osu.Framework/Platform/Apple/Native/NSAutoreleasePool.cs
Normal file
37
osu.Framework/Platform/Apple/Native/NSAutoreleasePool.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
internal readonly struct NSAutoreleasePool : IDisposable
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
|
||||
internal NSAutoreleasePool(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
private static readonly IntPtr class_pointer = Class.Get("NSAutoreleasePool");
|
||||
private static readonly IntPtr sel_alloc = Selector.Get("alloc");
|
||||
private static readonly IntPtr sel_init = Selector.Get("init");
|
||||
private static readonly IntPtr sel_drain = Selector.Get("drain");
|
||||
|
||||
public static NSAutoreleasePool Init()
|
||||
{
|
||||
var pool = alloc();
|
||||
Interop.SendIntPtr(pool.Handle, sel_init);
|
||||
return pool;
|
||||
}
|
||||
|
||||
private static NSAutoreleasePool alloc() => new NSAutoreleasePool(Interop.SendIntPtr(class_pointer, sel_alloc));
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != IntPtr.Zero)
|
||||
Interop.SendIntPtr(Handle, sel_drain);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,14 @@
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Platform.MacOS.Native;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
internal readonly struct NSData : IDisposable
|
||||
internal readonly struct NSData
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
|
||||
private static readonly IntPtr class_pointer = Class.Get("NSData");
|
||||
private static readonly IntPtr sel_release = Selector.Get("release");
|
||||
private static readonly IntPtr sel_data_with_bytes = Selector.Get("dataWithBytes:length:");
|
||||
private static readonly IntPtr sel_bytes = Selector.Get("bytes");
|
||||
private static readonly IntPtr sel_length = Selector.Get("length");
|
||||
@@ -22,6 +20,8 @@ namespace osu.Framework.Platform.Apple.Native
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
public static implicit operator NSData(NSMutableData data) => new NSData(data.Handle);
|
||||
|
||||
internal byte[] ToBytes()
|
||||
{
|
||||
IntPtr pointer = Interop.SendIntPtr(Handle, sel_bytes);
|
||||
@@ -32,14 +32,6 @@ namespace osu.Framework.Platform.Apple.Native
|
||||
return bytes;
|
||||
}
|
||||
|
||||
internal void Release() => Interop.SendVoid(Handle, sel_release);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != IntPtr.Zero)
|
||||
Release();
|
||||
}
|
||||
|
||||
internal static unsafe NSData FromBytes(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
fixed (byte* ptr = bytes)
|
||||
|
||||
29
osu.Framework/Platform/Apple/Native/NSMutableData.cs
Normal file
29
osu.Framework/Platform/Apple/Native/NSMutableData.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
internal readonly struct NSMutableData
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
|
||||
private static readonly IntPtr class_pointer = Class.Get("NSMutableData");
|
||||
private static readonly IntPtr sel_data_with_length = Selector.Get("dataWithLength:");
|
||||
private static readonly IntPtr sel_mutable_bytes = Selector.Get("mutableBytes");
|
||||
|
||||
internal NSMutableData(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
internal unsafe byte* MutableBytes => (byte*)Interop.SendIntPtr(Handle, sel_mutable_bytes);
|
||||
|
||||
internal static NSMutableData FromLength(int length)
|
||||
{
|
||||
IntPtr handle = Interop.SendIntPtr(class_pointer, sel_data_with_length, length);
|
||||
return new NSMutableData(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Platform.MacOS.Native;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Platform.MacOS.Native;
|
||||
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Platform.Apple.Native;
|
||||
|
||||
namespace osu.Framework.Platform.MacOS.Native
|
||||
namespace osu.Framework.Platform.Apple.Native
|
||||
{
|
||||
internal static partial class Selector
|
||||
{
|
||||
@@ -741,6 +741,7 @@ namespace osu.Framework.Platform
|
||||
CacheStorage = GetDefaultGameStorage().GetStorageForDirectory("cache");
|
||||
|
||||
SetupForRun();
|
||||
game.SetupLogging(Storage, CacheStorage);
|
||||
|
||||
populateInputHandlers();
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace osu.Framework.Platform
|
||||
|
||||
void UpdateMousePosition(Vector2 position);
|
||||
|
||||
void StartTextInput(bool allowIme);
|
||||
void StartTextInput(TextInputProperties properties);
|
||||
void StopTextInput();
|
||||
void SetTextInputRect(RectangleF rectangle);
|
||||
void ResetIme();
|
||||
|
||||
@@ -35,9 +35,12 @@ namespace osu.Framework.Platform.MacOS
|
||||
using var stream = new MemoryStream();
|
||||
image.SaveAsTiff(stream);
|
||||
|
||||
using var nsData = NSData.FromBytes(stream.ToArray());
|
||||
using var nsImage = NSImage.LoadFromData(nsData);
|
||||
return setToPasteboard(nsImage.Handle);
|
||||
using (NSAutoreleasePool.Init())
|
||||
{
|
||||
var nsData = NSData.FromBytes(stream.ToArray());
|
||||
using var nsImage = NSImage.LoadFromData(nsData);
|
||||
return setToPasteboard(nsImage.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr getFromPasteboard(IntPtr @class)
|
||||
|
||||
@@ -7,10 +7,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Input.Handlers.Mouse;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform.MacOS.Native;
|
||||
|
||||
@@ -44,6 +46,9 @@ namespace osu.Framework.Platform.MacOS
|
||||
|
||||
protected override ReadableKeyCombinationProvider CreateReadableKeyCombinationProvider() => new MacOSReadableKeyCombinationProvider();
|
||||
|
||||
public override IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore)
|
||||
=> new MacOSTextureLoaderStore(underlyingStore);
|
||||
|
||||
protected override void Swap()
|
||||
{
|
||||
base.Swap();
|
||||
|
||||
40
osu.Framework/Platform/MacOS/MacOSTextureLoaderStore.cs
Normal file
40
osu.Framework/Platform/MacOS/MacOSTextureLoaderStore.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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.IO;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform.Apple;
|
||||
using osu.Framework.Platform.Apple.Native;
|
||||
using osu.Framework.Platform.MacOS.Native;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace osu.Framework.Platform.MacOS
|
||||
{
|
||||
internal class MacOSTextureLoaderStore : AppleTextureLoaderStore
|
||||
{
|
||||
public MacOSTextureLoaderStore(IResourceStore<byte[]> store)
|
||||
: base(store)
|
||||
{
|
||||
}
|
||||
|
||||
protected override unsafe Image<TPixel> ImageFromStream<TPixel>(Stream stream)
|
||||
{
|
||||
using (NSAutoreleasePool.Init())
|
||||
{
|
||||
int length = (int)(stream.Length - stream.Position);
|
||||
var nativeData = NSMutableData.FromLength(length);
|
||||
|
||||
var bytesSpan = new Span<byte>(nativeData.MutableBytes, length);
|
||||
stream.ReadExactly(bytesSpan);
|
||||
|
||||
using var nsImage = NSImage.LoadFromData(nativeData);
|
||||
if (nsImage.Handle == IntPtr.Zero)
|
||||
throw new ArgumentException($"{nameof(Image)} could not be created from {nameof(stream)}.");
|
||||
|
||||
var cgImage = nsImage.CGImage;
|
||||
return ImageFromCGImage<TPixel>(cgImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@ namespace osu.Framework.Platform.MacOS.Native
|
||||
private static readonly IntPtr sel_release = Selector.Get("release");
|
||||
private static readonly IntPtr sel_init_with_data = Selector.Get("initWithData:");
|
||||
private static readonly IntPtr sel_tiff_representation = Selector.Get("TIFFRepresentation");
|
||||
private static readonly IntPtr sel_cg_image_for_proposed_rect = Selector.Get("CGImageForProposedRect:context:hints:");
|
||||
|
||||
internal CGImage CGImage => new CGImage(Interop.SendIntPtr(Handle, sel_cg_image_for_proposed_rect, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero));
|
||||
|
||||
internal NSData TiffRepresentation => new NSData(Interop.SendIntPtr(Handle, sel_tiff_representation));
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Platform.Apple.Native;
|
||||
using osu.Framework.Platform.MacOS.Native;
|
||||
using osu.Framework.Platform.SDL2;
|
||||
using osuTK;
|
||||
using Selector = osu.Framework.Platform.Apple.Native.Selector;
|
||||
|
||||
namespace osu.Framework.Platform.MacOS
|
||||
{
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Platform.Apple.Native;
|
||||
using osu.Framework.Platform.MacOS.Native;
|
||||
using osu.Framework.Platform.SDL3;
|
||||
using osuTK;
|
||||
using Selector = osu.Framework.Platform.Apple.Native.Selector;
|
||||
|
||||
namespace osu.Framework.Platform.MacOS
|
||||
{
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace osu.Framework.Platform.SDL2
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void StartTextInput(bool allowIme) => ScheduleCommand(SDL_StartTextInput);
|
||||
public virtual void StartTextInput(TextInputProperties properties) => ScheduleCommand(SDL_StartTextInput);
|
||||
|
||||
public void StopTextInput() => ScheduleCommand(SDL_StopTextInput);
|
||||
|
||||
|
||||
@@ -1011,6 +1011,34 @@ namespace osu.Framework.Platform.SDL3
|
||||
w = rectangle.Width,
|
||||
};
|
||||
|
||||
public static SDL_TextInputType ToSDLTextInputType(this TextInputType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
default:
|
||||
case TextInputType.Text:
|
||||
return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT;
|
||||
|
||||
case TextInputType.Name:
|
||||
return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT_NAME;
|
||||
|
||||
case TextInputType.EmailAddress:
|
||||
return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT_EMAIL;
|
||||
|
||||
case TextInputType.Username:
|
||||
return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT_USERNAME;
|
||||
|
||||
case TextInputType.Number:
|
||||
return SDL_TextInputType.SDL_TEXTINPUT_TYPE_NUMBER;
|
||||
|
||||
case TextInputType.Password:
|
||||
return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN;
|
||||
|
||||
case TextInputType.NumericalPassword:
|
||||
return SDL_TextInputType.SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN;
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe DisplayMode ToDisplayMode(this SDL_DisplayMode mode, int displayIndex)
|
||||
{
|
||||
int bpp;
|
||||
|
||||
@@ -185,7 +185,22 @@ namespace osu.Framework.Platform.SDL3
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void StartTextInput(bool allowIme) => ScheduleCommand(() => SDL_StartTextInput(SDLWindowHandle));
|
||||
private SDL_PropertiesID? currentTextInputProperties;
|
||||
|
||||
public virtual void StartTextInput(TextInputProperties properties) => ScheduleCommand(() =>
|
||||
{
|
||||
currentTextInputProperties ??= SDL_CreateProperties();
|
||||
|
||||
var props = currentTextInputProperties.Value;
|
||||
SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, (long)properties.Type.ToSDLTextInputType());
|
||||
|
||||
if (!properties.AutoCapitalisation)
|
||||
SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, (long)SDL_Capitalization.SDL_CAPITALIZE_NONE);
|
||||
else
|
||||
SDL_ClearProperty(props, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER);
|
||||
|
||||
SDL_StartTextInputWithProperties(SDLWindowHandle, props);
|
||||
});
|
||||
|
||||
public void StopTextInput() => ScheduleCommand(() => SDL_StopTextInput(SDLWindowHandle));
|
||||
|
||||
@@ -196,7 +211,11 @@ namespace osu.Framework.Platform.SDL3
|
||||
public virtual void ResetIme() => ScheduleCommand(() =>
|
||||
{
|
||||
SDL_StopTextInput(SDLWindowHandle);
|
||||
SDL_StartTextInput(SDLWindowHandle);
|
||||
|
||||
if (currentTextInputProperties is SDL_PropertiesID props)
|
||||
SDL_StartTextInputWithProperties(SDLWindowHandle, props);
|
||||
else
|
||||
SDL_StartTextInput(SDLWindowHandle);
|
||||
});
|
||||
|
||||
public void SetTextInputRect(RectangleF rect) => ScheduleCommand(() =>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Handlers.Mouse;
|
||||
using osu.Framework.Platform.SDL2;
|
||||
using osu.Framework.Platform.Windows.Native;
|
||||
@@ -138,10 +139,10 @@ namespace osu.Framework.Platform.Windows
|
||||
|
||||
#region IME handling
|
||||
|
||||
public override void StartTextInput(bool allowIme)
|
||||
public override void StartTextInput(TextInputProperties properties)
|
||||
{
|
||||
base.StartTextInput(allowIme);
|
||||
ScheduleCommand(() => Imm.SetImeAllowed(WindowHandle, allowIme));
|
||||
base.StartTextInput(properties);
|
||||
ScheduleCommand(() => Imm.SetImeAllowed(WindowHandle, properties.Type.SupportsIme() && properties.AllowIme));
|
||||
}
|
||||
|
||||
public override void ResetIme() => ScheduleCommand(() => Imm.CancelComposition(WindowHandle));
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Handlers.Mouse;
|
||||
using osu.Framework.Platform.SDL3;
|
||||
using osu.Framework.Platform.Windows.Native;
|
||||
@@ -92,10 +93,10 @@ namespace osu.Framework.Platform.Windows
|
||||
}
|
||||
}
|
||||
|
||||
public override void StartTextInput(bool allowIme)
|
||||
public override void StartTextInput(TextInputProperties properties)
|
||||
{
|
||||
base.StartTextInput(allowIme);
|
||||
ScheduleCommand(() => Imm.SetImeAllowed(WindowHandle, allowIme));
|
||||
base.StartTextInput(properties);
|
||||
ScheduleCommand(() => Imm.SetImeAllowed(WindowHandle, properties.Type.SupportsIme() && properties.AllowIme));
|
||||
}
|
||||
|
||||
public override void ResetIme() => ScheduleCommand(() => Imm.CancelComposition(WindowHandle));
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace osu.Framework.Testing.Input
|
||||
{
|
||||
public class ManualTextInputSource : TextInputSource
|
||||
{
|
||||
public readonly Queue<bool> ActivationQueue = new Queue<bool>();
|
||||
public readonly Queue<bool> EnsureActivatedQueue = new Queue<bool>();
|
||||
public readonly Queue<TextInputProperties> ActivationQueue = new Queue<TextInputProperties>();
|
||||
public readonly Queue<TextInputProperties> EnsureActivatedQueue = new Queue<TextInputProperties>();
|
||||
public readonly Queue<bool> DeactivationQueue = new Queue<bool>();
|
||||
|
||||
public void Text(string text) => TriggerTextInput(text);
|
||||
@@ -32,16 +32,16 @@ namespace osu.Framework.Testing.Input
|
||||
base.TriggerImeComposition(string.Empty, 0, 0);
|
||||
}
|
||||
|
||||
protected override void ActivateTextInput(bool allowIme)
|
||||
protected override void ActivateTextInput(TextInputProperties properties)
|
||||
{
|
||||
base.ActivateTextInput(allowIme);
|
||||
ActivationQueue.Enqueue(allowIme);
|
||||
base.ActivateTextInput(properties);
|
||||
ActivationQueue.Enqueue(properties);
|
||||
}
|
||||
|
||||
protected override void EnsureTextInputActivated(bool allowIme)
|
||||
protected override void EnsureTextInputActivated(TextInputProperties properties)
|
||||
{
|
||||
base.EnsureTextInputActivated(allowIme);
|
||||
EnsureActivatedQueue.Enqueue(allowIme);
|
||||
base.EnsureTextInputActivated(properties);
|
||||
EnsureActivatedQueue.Enqueue(properties);
|
||||
}
|
||||
|
||||
protected override void DeactivateTextInput()
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<PackageReference Include="ppy.osuTK.NS20" Version="1.0.211" />
|
||||
<PackageReference Include="StbiSharp" Version="1.1.0" />
|
||||
<PackageReference Include="ppy.SDL2-CS" Version="1.0.741-alpha" />
|
||||
<PackageReference Include="ppy.SDL3-CS" Version="2024.1128.0" />
|
||||
<PackageReference Include="ppy.SDL3-CS" Version="2025.104.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.SourceGeneration" Version="2024.1128.0" />
|
||||
|
||||
<!-- DO NOT use ProjectReference for native packaging project.
|
||||
|
||||
Reference in New Issue
Block a user