Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sources/engine/Stride.Graphics/SpriteBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ private void DrawString(SpriteFont spriteFont, ref SpriteFont.StringProxy text,
drawCommand.Position.X /= resolutionRatio.X;
drawCommand.Position.Y /= resolutionRatio.Y;

spriteFont.InternalDraw(commandList, ref text, ref drawCommand, alignment);
spriteFont.InternalDraw(commandList, text, ref drawCommand, alignment);
}

internal unsafe void DrawSprite(Texture texture, ref RectangleF destination, bool scaleDestination, ref RectangleF? sourceRectangle, Color4 color, Color4 colorAdd,
Expand Down
361 changes: 213 additions & 148 deletions sources/engine/Stride.Graphics/SpriteFont.cs

Large diffs are not rendered by default.

25 changes: 4 additions & 21 deletions sources/engine/Stride.Graphics/UIBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System;
using System.Runtime.InteropServices;

using Stride.Core.Annotations;
using Stride.Core.Mathematics;
using Stride.Rendering;

Expand Down Expand Up @@ -426,7 +426,7 @@ internal void DrawCharacter(Texture texture, in Matrix worldViewProjectionMatrix
Draw(texture, in elementInfo);
}

internal void DrawString(SpriteFont font, string text, ref SpriteFont.InternalUIDrawCommand drawCommand)
internal void DrawString([NotNull] SpriteFont font, [NotNull] string text, ref SpriteFont.InternalUIDrawCommand drawCommand)
{
if (font == null) throw new ArgumentNullException(nameof(font));
if (text == null) throw new ArgumentNullException(nameof(text));
Expand All @@ -443,24 +443,7 @@ internal void DrawString(SpriteFont font, string text, ref SpriteFont.InternalUI
// transform the world matrix into the world view project matrix
Matrix.Multiply(ref worldMatrix, ref viewProjectionMatrix, out drawCommand.Matrix);

if (font.FontType == SpriteFontType.SDF)
{
drawCommand.SnapText = false;
float scaling = drawCommand.RequestedFontSize / font.Size;
drawCommand.RealVirtualResolutionRatio = 1 / new Vector2(scaling, scaling);
}
if (font.FontType == SpriteFontType.Static)
{
drawCommand.RealVirtualResolutionRatio = Vector2.One; // ensure that static font are not scaled internally
}
if (font.FontType == SpriteFontType.Dynamic)
{
// Dynamic: if we're not displaying in a situation where we can snap text, we're probably in 3D.
// Let's use virtual resolution (otherwise requested size might change on every camera move)
// TODO: some step function to have LOD without regenerating on every small change?
if (!drawCommand.SnapText)
drawCommand.RealVirtualResolutionRatio = Vector2.One;
}
font.TypeSpecificRatios(drawCommand.RequestedFontSize, ref drawCommand.SnapText, ref drawCommand.RealVirtualResolutionRatio, out var actualFontSize);

// snap draw start position to prevent characters to be drawn in between two pixels
if (drawCommand.SnapText)
Expand All @@ -477,7 +460,7 @@ internal void DrawString(SpriteFont font, string text, ref SpriteFont.InternalUI
drawCommand.Matrix.M42 /= invW;
}

font.InternalUIDraw(GraphicsContext.CommandList, ref proxy, ref drawCommand);
font.InternalUIDraw(GraphicsContext.CommandList, proxy, ref drawCommand, actualFontSize);
}

protected override unsafe void UpdateBufferValuesFromElementInfo(ref ElementInfo elementInfo, IntPtr vertexPtr, IntPtr indexPtr, int vertexOffset)
Expand Down
60 changes: 15 additions & 45 deletions sources/engine/Stride.UI/Controls/EditText.Direct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public partial class EditText
{
private void OnTouchMoveImpl(TouchEventArgs args)
{
var currentPosition = FindNearestCharacterIndex(new Vector2(args.WorldPosition.X - WorldMatrix.M41, args.WorldPosition.Y - WorldMatrix.M42));
var currentPosition = FindNearestCharacterIndex(args.WorldPosition.XY());

if (caretAtStart)
{
Expand All @@ -36,62 +36,32 @@ private void OnTouchMoveImpl(TouchEventArgs args)
private void OnTouchDownImpl(TouchEventArgs args)
{
// Find the appropriate position for the caret.
CaretPosition = FindNearestCharacterIndex(new Vector2(args.WorldPosition.X - WorldMatrix.M41, args.WorldPosition.Y - WorldMatrix.M42));
CaretPosition = FindNearestCharacterIndex(args.WorldPosition.XY());
}

/// <summary>
/// Find the index of the nearest character to the provided position.
/// Find the index of the nearest character to the provided position in <see cref="TouchEventArgs.WorldPosition"/> space
/// </summary>
/// <param name="position">The position in edit text space</param>
/// <returns>The 0-based index of the nearest character</returns>
protected virtual int FindNearestCharacterIndex(Vector2 position)
protected virtual int FindNearestCharacterIndex(Vector2 worldPosition)
{
if (Font == null)
return 0;

var textRegionSize = (ActualWidth - Padding.Left - Padding.Right);
var fontScale = LayoutingContext.RealVirtualResolutionRatio;
var fontSize = new Vector2(fontScale.Y * ActualTextSize); // we don't want letters non-uniform ratio
var textRegion = GetTextRegionSize();
var regionHalf = textRegion / 2;
var worldMatrix = WorldMatrix;

// calculate the offset of the beginning of the text due to text alignment
var alignmentOffset = -textRegionSize / 2f;
if (TextAlignment != TextAlignment.Left)
{
var textWidth = Font.MeasureString(TextToDisplay, ref fontSize).X;
if (Font.FontType == SpriteFontType.Dynamic)
textWidth /= fontScale.X;

alignmentOffset = TextAlignment == TextAlignment.Center ? -textWidth / 2 : -textRegionSize / 2f + (textRegionSize - textWidth);
}
var touchInText = position.X - alignmentOffset;
var offset = worldMatrix.TranslationVector;
// Text draws from the upper left corner of the rect, let's account for that
offset -= worldMatrix.Right * regionHalf.X - worldMatrix.Up * regionHalf.Y;
worldPosition -= offset.XY();

// Find the first character starting after the click
var characterIndex = 1;
var previousCharacterOffset = 0f;
var currentCharacterOffset = Font.MeasureString(TextToDisplay, ref fontSize, characterIndex).X;
while (currentCharacterOffset < touchInText && characterIndex < textToDisplay.Length)
{
++characterIndex;
previousCharacterOffset = currentCharacterOffset;
currentCharacterOffset = Font.MeasureString(TextToDisplay, ref fontSize, characterIndex).X;
if (Font.FontType == SpriteFontType.Dynamic)
currentCharacterOffset /= fontScale.X;
}
var snapText = false;
var realVirtualResolutionRatio = LayoutingContext.RealVirtualResolutionRatio;

// determine the caret position.
if (touchInText < 0) // click before the start of the text
{
return 0;
}
if (currentCharacterOffset < touchInText) // click after the end of the text
{
return textToDisplay.Length;
}
Font.TypeSpecificRatios(ActualTextSize, ref snapText, ref realVirtualResolutionRatio, out var fontSize);

const float Alpha = 0.66f;
var previousElementRatio = Math.Abs(touchInText - previousCharacterOffset) / Alpha;
var currentElementRation = Math.Abs(currentCharacterOffset - touchInText) / (1 - Alpha);
return previousElementRatio < currentElementRation ? characterIndex - 1 : characterIndex;
return Font.IndexInString(TextToDisplay, fontSize, worldPosition, (TextAlignment, textRegion));
}

internal override void OnKeyPressed(KeyEventArgs args)
Expand Down
13 changes: 10 additions & 3 deletions sources/engine/Stride.UI/Controls/EditText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -321,15 +321,15 @@ public float CaretWidth
/// <userdoc>The color of the selection.</userdoc>
[DataMember]
[Display(category: AppearanceCategory)]
public Color SelectionColor { get; set; } = Color.FromAbgr(0xF0F0F0FF);
public Color SelectionColor { get; set; } = Color.FromAbgr(0x80B4D5FF);

/// <summary>
/// Gets or sets the color of the IME composition selection.
/// </summary>
/// <userdoc>The color of the selection.</userdoc>
[DataMember]
[Display(category: AppearanceCategory)]
public Color IMESelectionColor { get; set; } = Color.FromAbgr(0xF0FFF0FF);
public Color IMESelectionColor { get; set; } = Color.FromAbgr(0x80FFF0FF);

/// <summary>
/// Gets or sets whether the control is read-only, or not.
Expand Down Expand Up @@ -896,5 +896,12 @@ protected override void OnTouchMove(TouchEventArgs args)
OnTouchMoveImpl(args);
}
}

internal Vector2 GetTextRegionSize()
{
return new Vector2(
ActualWidth - Padding.Left - Padding.Right,
ActualHeight - Padding.Top - Padding.Bottom);
}
}
}
}
2 changes: 1 addition & 1 deletion sources/engine/Stride.UI/Controls/TextBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ private Vector2 CalculateTextSize(SpriteFont.StringProxy textToMeasure)

var sizeRatio = LayoutingContext.RealVirtualResolutionRatio;
var measureFontSize = new Vector2(sizeRatio.Y * ActualTextSize); // we don't want letters non-uniform ratio
var realSize = Font.MeasureString(ref textToMeasure, ref measureFontSize);
var realSize = Font.MeasureString(textToMeasure, measureFontSize);

// force pre-generation if synchronous generation is required
if (SynchronousCharacterGeneration)
Expand Down
115 changes: 63 additions & 52 deletions sources/engine/Stride.UI/Renderers/DefaultEditTextRenderer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using System;
using Stride.Core.Annotations;
using Stride.Core.Mathematics;
using Stride.Graphics;
using Stride.Graphics.Font;
Expand All @@ -20,54 +22,72 @@ public DefaultEditTextRenderer(IServiceRegistry services)
{
}

private void RenderSelection(EditText editText, UIRenderingContext context, int start, int length, Color color, out float offsetTextStart, out float offsetAlignment, out float selectionSize)
private void RenderSelection([NotNull] EditText editText, [NotNull] UIRenderingContext context, in Vector2 textRegion, int start, int length, Color color, out Matrix caret, out float caretHeight)
{
// calculate the size of the text region by removing padding
var textRegionSize = new Vector2(editText.ActualWidth - editText.Padding.Left - editText.Padding.Right,
editText.ActualHeight - editText.Padding.Top - editText.Padding.Bottom);

var snapText = context.ShouldSnapText && !editText.DoNotSnapText;
var requestedFontSize = editText.ActualTextSize;
var realVirtualResolutionRatio = editText.LayoutingContext.RealVirtualResolutionRatio;
var font = editText.Font;

// determine the image to draw in background of the edit text
var fontScale = editText.LayoutingContext.RealVirtualResolutionRatio;
var provider = editText.IsSelectionActive ? editText.ActiveImage : editText.MouseOverState == MouseOverState.MouseOverElement ? editText.MouseOverImage : editText.InactiveImage;
var image = provider?.GetSprite();
font.TypeSpecificRatios(requestedFontSize, ref snapText, ref realVirtualResolutionRatio, out var fontSize);

var lineHeight = font.GetTotalLineSpacing(fontSize.Y);

var fontSize = new Vector2(fontScale.Y * editText.ActualTextSize);
offsetTextStart = font.MeasureString(editText.TextToDisplay, ref fontSize, start).X;
selectionSize = font.MeasureString(editText.TextToDisplay, ref fontSize, start + length).X - offsetTextStart;
var lineSpacing = font.GetTotalLineSpacing(editText.ActualTextSize);
if (font.FontType == SpriteFontType.Dynamic)
var worldMatrix = editText.WorldMatrixInternal;
worldMatrix.TranslationVector -= worldMatrix.Right * textRegion.X * 0.5f + worldMatrix.Up * (textRegion.Y * 0.5f - lineHeight * 0.5f);

Vector2 selectionStart = default, selectionEnd = default, lineStart = default, lineEnd = default;
var end = start + length;
foreach (var glyphInfo in new SpriteFont.GlyphEnumerator(null, new SpriteFont.StringProxy(editText.TextToDisplay), fontSize, false, 0, editText.TextToDisplay.Length, font, (editText.TextAlignment, textRegion)))
{
offsetTextStart /= fontScale.X;
selectionSize /= fontScale.X;
if (glyphInfo.Index < start)
{
lineEnd = lineStart = selectionEnd = selectionStart = new Vector2(glyphInfo.NextX, glyphInfo.Position.Y);
}
else if (glyphInfo.Index == start)
{
lineStart = selectionEnd = selectionStart = glyphInfo.Position;
lineEnd = new Vector2(glyphInfo.NextX, glyphInfo.Position.Y);
}
else if (glyphInfo.Index <= end)
{
// We're between start and end
if (lineStart.Y != glyphInfo.Y) // Skipped a line, draw a selection rect between the edges of the previous line
{
DrawSelectionOnGlyphRange(context, color, worldMatrix, lineStart, lineEnd, lineHeight);
lineStart = glyphInfo.Position;
}

lineEnd = new Vector2(glyphInfo.NextX, glyphInfo.Position.Y);
if (glyphInfo.Index < end)
selectionEnd = new Vector2(glyphInfo.NextX, glyphInfo.Position.Y);
else
selectionEnd = glyphInfo.Position;
}
else
{
break;
}
}

var scaleRatio = editText.ActualTextSize / font.Size;
if (font.FontType == SpriteFontType.SDF)
if (end == editText.TextToDisplay.Length) // Edge case for single character selected at the end of a string
{
offsetTextStart *= scaleRatio;
selectionSize *= scaleRatio;
lineSpacing *= editText.ActualTextSize / font.Size;
selectionEnd.X = lineEnd.X;
}

DrawSelectionOnGlyphRange(context, color, worldMatrix, lineStart, selectionEnd, lineHeight);

offsetAlignment = -textRegionSize.X / 2f;
if (editText.TextAlignment != TextAlignment.Left)
{
var textWidth = font.MeasureString(editText.TextToDisplay, ref fontSize).X;
if (font.FontType == SpriteFontType.Dynamic)
textWidth /= fontScale.X;
if (font.FontType == SpriteFontType.SDF)
textWidth *= scaleRatio;

offsetAlignment = editText.TextAlignment == TextAlignment.Center ? -textWidth / 2 : -textRegionSize.X / 2f + (textRegionSize.X - textWidth);
}
caretHeight = lineHeight;
caret = worldMatrix;
caret.TranslationVector += caret.Right * selectionStart.X + caret.Up * selectionStart.Y;
}

var selectionWorldMatrix = editText.WorldMatrixInternal;
selectionWorldMatrix.M41 += offsetTextStart + selectionSize / 2 + offsetAlignment;
var selectionScaleVector = new Vector3(selectionSize, editText.LineCount * lineSpacing, 0);
Batch.DrawRectangle(ref selectionWorldMatrix, ref selectionScaleVector, ref color, context.DepthBias + 1);
private void DrawSelectionOnGlyphRange(UIRenderingContext context, Color color, in Matrix worldMatrix, Vector2 start, Vector2 end, float lineHeight)
{
var tempMatrix = worldMatrix;
var selectionRect = new Vector3(end.X - start.X, lineHeight, 0);
tempMatrix.TranslationVector += worldMatrix.Right * (start.X + selectionRect.X * 0.5f) + worldMatrix.Up * start.Y;
Batch.DrawRectangle(ref tempMatrix, ref selectionRect, ref color, context.DepthBias + 1);
}

public override void RenderColor(UIElement element, UIRenderingContext context)
Expand All @@ -91,27 +111,25 @@ public override void RenderColor(UIElement element, UIRenderingContext context)
}

// calculate the size of the text region by removing padding
var textRegionSize = new Vector2(editText.ActualWidth - editText.Padding.Left - editText.Padding.Right,
editText.ActualHeight - editText.Padding.Top - editText.Padding.Bottom);
var textRegionSize = editText.GetTextRegionSize();

var font = editText.Font;
var caretColor = editText.RenderOpacity * editText.CaretColor;

var offsetTextStart = 0f;
var offsetAlignment = 0f;
var selectionSize = 0f;
var caretMatrix = Matrix.Identity;
var caretHeight = 0f;

// Draw the composition selection
if (editText.Composition.Length > 0)
{
var imeSelectionColor = editText.RenderOpacity * editText.IMESelectionColor;
RenderSelection(editText, context, editText.SelectionStart, editText.Composition.Length, imeSelectionColor, out offsetTextStart, out offsetAlignment, out selectionSize);
RenderSelection(editText, context, textRegionSize, editText.SelectionStart, editText.Composition.Length, imeSelectionColor, out caretMatrix, out caretHeight);
}
// Draw the regular selection
else if (editText.IsSelectionActive)
{
var selectionColor = editText.RenderOpacity * editText.SelectionColor;
RenderSelection(editText, context, editText.SelectionStart, editText.SelectionLength, selectionColor, out offsetTextStart, out offsetAlignment, out selectionSize);
RenderSelection(editText, context, textRegionSize, editText.SelectionStart, editText.SelectionLength, selectionColor, out caretMatrix, out caretHeight);
}

// create the text draw command
Expand Down Expand Up @@ -146,15 +164,8 @@ public override void RenderColor(UIElement element, UIRenderingContext context)
// Draw the cursor
if (editText.IsCaretVisible)
{
var lineSpacing = editText.Font.GetTotalLineSpacing(editText.ActualTextSize);
if (editText.Font.FontType == SpriteFontType.SDF)
lineSpacing *= editText.ActualTextSize / font.Size;

var sizeCaret = editText.CaretWidth / fontScale.X;
var caretWorldMatrix = element.WorldMatrixInternal;
caretWorldMatrix.M41 += offsetTextStart + offsetAlignment + (editText.CaretPosition > editText.SelectionStart? selectionSize: 0);
var caretScaleVector = new Vector3(sizeCaret, editText.LineCount * lineSpacing, 0);
Batch.DrawRectangle(ref caretWorldMatrix, ref caretScaleVector, ref caretColor, context.DepthBias + 3);
var caretScaleVector = new Vector3(editText.CaretWidth / fontScale.X, caretHeight, 0);
Batch.DrawRectangle(ref caretMatrix, ref caretScaleVector, ref caretColor, context.DepthBias + 3);
}
}
}
Expand Down
Loading