Skip to content

Commit

Permalink
feat(skia): Support RTL FlowDirection
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 authored and ahmed605 committed Sep 13, 2023
1 parent 717177d commit 912de33
Show file tree
Hide file tree
Showing 15 changed files with 158 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<!-- Back Button -->
Expand Down Expand Up @@ -558,6 +559,10 @@
IsChecked="{Binding UseDarkTheme, Mode=TwoWay}">
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}" Glyph="&#xEC46;" />
</CheckBox>

<!-- Fluent styles check box -->
<CheckBox x:Name="RTLCheckBox" Content="RTL"
Grid.Column="10" />
</Grid>

<!-- Sample Content -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
using System.Runtime.InteropServices.WindowsRuntime;
using SampleControl.Presentation;
using Windows.Foundation;
using Windows.UI.Xaml;
using Microsoft.VisualStudio.TestTools.UnitTesting;
#if NETFX_CORE
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
Expand All @@ -30,6 +30,8 @@ public sealed partial class SampleChooserControl : UserControl
public SampleChooserControl()
{
this.InitializeComponent();
RTLCheckBox.Checked += (_, _) => this.FlowDirection = FlowDirection.RightToLeft;
RTLCheckBox.Unchecked += (_, _) => this.FlowDirection = FlowDirection.LeftToRight;
}

protected override Size MeasureOverride(Size availableSize)
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.CrossTargetting.targets
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
</PropertyGroup>

<PropertyGroup Condition="$(_IsNetStd) and '$(UnoRuntimeIdentifier)'=='Skia'">
<DefineConstants>$(DefineConstants);__SKIA__;UNO_REFERENCE_API;UNO_HAS_ENHANCED_HIT_TEST_PROPERTY;UNO_HAS_MANAGED_SCROLL_PRESENTER;UNO_HAS_MANAGED_POINTERS</DefineConstants>
<DefineConstants>$(DefineConstants);__SKIA__;UNO_REFERENCE_API;UNO_HAS_ENHANCED_HIT_TEST_PROPERTY;UNO_HAS_MANAGED_SCROLL_PRESENTER;UNO_HAS_MANAGED_POINTERS;SUPPORTS_RTL</DefineConstants>
<DefineConstants>$(DefineConstants);UNO_SUPPORTS_NATIVEHOST</DefineConstants>
</PropertyGroup>

Expand Down
22 changes: 0 additions & 22 deletions src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml/FrameworkElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,20 +151,6 @@ public double Height
this.SetValue(HeightProperty, value);
}
}
#endif
#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public global::Windows.UI.Xaml.FlowDirection FlowDirection
{
get
{
return (global::Windows.UI.Xaml.FlowDirection)this.GetValue(FlowDirectionProperty);
}
set
{
this.SetValue(FlowDirectionProperty, value);
}
}
#endif
// Skipping already declared property ActualHeight
// Skipping already declared property ActualWidth
Expand Down Expand Up @@ -206,14 +192,6 @@ public double Height
typeof(global::Windows.UI.Xaml.FrameworkElement),
new Windows.UI.Xaml.FrameworkPropertyMetadata(default(double)));
#endif
#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public static global::Windows.UI.Xaml.DependencyProperty FlowDirectionProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
nameof(FlowDirection), typeof(global::Windows.UI.Xaml.FlowDirection),
typeof(global::Windows.UI.Xaml.FrameworkElement),
new Windows.UI.Xaml.FrameworkPropertyMetadata(default(global::Windows.UI.Xaml.FlowDirection)));
#endif
#if false || false || false || false || false || __NETSTD_REFERENCE__ || false
[global::Uno.NotImplemented("__NETSTD_REFERENCE__")]
public static global::Windows.UI.Xaml.DependencyProperty HeightProperty { get; } =
Expand Down
23 changes: 22 additions & 1 deletion src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,38 @@ protected override Size MeasureOverride(Size availableSize)
return desiredSize.Add(padding);
}

private void ApplyFlowDirection(float width)
{
if (this.FlowDirection == FlowDirection.RightToLeft)
{
_textVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, width, 0.0f));
}
else
{
_textVisual.TransformMatrix = Matrix4x4.Identity;
}
}

protected override Size ArrangeOverride(Size finalSize)
{
var padding = Padding;
var availableSizeWithoutPadding = finalSize.Subtract(padding);
var arrangedSizeWithoutPadding = Inlines.Arrange(availableSizeWithoutPadding);
_textVisual.Size = new Vector2((float)arrangedSizeWithoutPadding.Width, (float)arrangedSizeWithoutPadding.Height);
_textVisual.Offset = new Vector3((float)padding.Left, (float)padding.Top, 0);

ApplyFlowDirection((float)finalSize.Width);
return base.ArrangeOverride(finalSize);
}

internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args)
{
base.OnPropertyChanged2(args);
if (args.Property == FlowDirectionProperty)
{
ApplyFlowDirection((float)this.RenderSize.Width);
}
}

private Hyperlink? FindHyperlinkAt(Point point)
{
var padding = Padding;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ public void UpdatePosition()

var transformToRoot = _contentElement.TransformToVisual(Windows.UI.Xaml.Window.Current.Content);
var point = transformToRoot.TransformPoint(new Point(_contentElement.Padding.Left, _contentElement.Padding.Top));
var pointX = (int)point.X;
var pointX = _owner?.TextBox?.FlowDirection is FlowDirection.RightToLeft
? (int)(point.X - _contentElement.RenderSize.Width)
: (int)point.X;
var pointY = (int)point.Y;

if (_lastPosition.X != pointX || _lastPosition.Y != pointY)
Expand Down
12 changes: 12 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,18 @@ private void OnTextWrappingChanged()
partial void OnTextWrappingChangedPartial();

#endregion
#if SUPPORTS_RTL
internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args)
{
base.OnPropertyChanged2(args);
if (args.Property == FrameworkElement.FlowDirectionProperty)
{
OnFlowDirectionChangedPartial();
}
}

partial void OnFlowDirectionChangedPartial();
#endif

#if __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
[Uno.NotImplemented("__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
Expand Down
10 changes: 10 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ public partial class TextBox

partial void OnMaxLengthChangedPartial(int newValue) => TextBoxView?.UpdateMaxLength();

partial void OnFlowDirectionChangedPartial()
{
TextBoxView?.SetFlowDirectionAndTextAlignment();
}

partial void OnTextAlignmentChangedPartial(TextAlignment newValue)
{
TextBoxView?.SetFlowDirectionAndTextAlignment();
}

private void UpdateTextBoxView()
{
_textBoxView ??= new TextBoxView(this);
Expand Down
28 changes: 27 additions & 1 deletion src/Uno.UI/UI/Xaml/Controls/TextBox/TextBoxView.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ internal class TextBoxView

public TextBoxView(TextBox textBox)
{
DisplayBlock = new TextBlock();
SetFlowDirectionAndTextAlignment();

_textBox = new WeakReference<TextBox>(textBox);
_isPasswordBox = textBox is PasswordBox;
if (!ApiExtensibility.CreateInstance(this, out _textBoxExtension))
Expand Down Expand Up @@ -54,7 +57,7 @@ public TextBox? TextBox

internal int GetSelectionLength() => _textBoxExtension?.GetSelectionLength() ?? 0;

public TextBlock DisplayBlock { get; } = new TextBlock();
public TextBlock DisplayBlock { get; }

internal void SetTextNative(string text)
{
Expand All @@ -68,6 +71,29 @@ internal void Select(int start, int length)
_textBoxExtension?.Select(start, length);
}

internal void SetFlowDirectionAndTextAlignment()
{
if (_textBox?.GetTarget() is not { } textBox)
{
return;
}

var flowDirection = textBox.FlowDirection;
var textAlignment = textBox.TextAlignment;
if (flowDirection == FlowDirection.RightToLeft)
{
textAlignment = textAlignment switch
{
TextAlignment.Left => TextAlignment.Right,
TextAlignment.Right => TextAlignment.Left,
_ => textAlignment,
};
}

DisplayBlock.FlowDirection = flowDirection;
DisplayBlock.TextAlignment = textAlignment;
}

internal void OnForegroundChanged(Brush brush)
{
DisplayBlock.Foreground = brush;
Expand Down
9 changes: 9 additions & 0 deletions src/Uno.UI/UI/Xaml/Documents/InlineCollection.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,15 @@ internal void Draw(in DrawingSession session)
var canvas = session.Surface.Canvas;
var parent = (IBlock)_collection.GetParent();
var alignment = parent.TextAlignment;
if (parent.FlowDirection == FlowDirection.RightToLeft)
{
alignment = alignment switch
{
TextAlignment.Left => TextAlignment.Right,
TextAlignment.Right => TextAlignment.Left,
_ => alignment,
};
}

float y = 0;

Expand Down
2 changes: 2 additions & 0 deletions src/Uno.UI/UI/Xaml/Documents/TextFormatting/IBlock.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ internal interface IBlock

TextWrapping TextWrapping { get; }

FlowDirection FlowDirection { get; }

int MaxLines { get; }
}
}
25 changes: 25 additions & 0 deletions src/Uno.UI/UI/Xaml/FrameworkElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,31 @@ private protected virtual void OnBackgroundSizingChangedInner(DependencyProperty

#endregion

#region FlowDirection Dependency Property
#if !SUPPORTS_RTL
[NotImplemented("__ANDROID__", "__IOS__", "__WASM__", "__MACOS__")]
#endif
public FlowDirection FlowDirection
{
get => GetFlowDirectionValue();
set => SetFlowDirectionValue(value);
}

#if !SUPPORTS_RTL
[NotImplemented("__ANDROID__", "__IOS__", "__WASM__", "__MACOS__")]
#endif
[GeneratedDependencyProperty(DefaultValue = FlowDirection.LeftToRight, Options = FrameworkPropertyMetadataOptions.Inherits, ChangedCallback = true)]
public static DependencyProperty FlowDirectionProperty { get; } = CreateFlowDirectionProperty();

private void OnFlowDirectionChanged(FlowDirection oldValue, FlowDirection newValue)
{
#if SUPPORTS_RTL
this.InvalidateArrange();
VisualTreeHelper.GetParent(this)?.InvalidateArrange();
#endif
}

#endregion

partial void Initialize()
{
Expand Down
3 changes: 3 additions & 0 deletions src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ private static (UIElement? element, Branch? stale) SearchDownForTopMostElementAt
element.ApplyRenderTransform(ref matrix);
element.ApplyLayoutTransform(ref matrix);
element.ApplyElementCustomTransform(ref matrix);
element.ApplyFlowDirectionTransform(ref matrix);

TRACE($"- transform to parent: [{matrix.M11:F2},{matrix.M12:F2} / {matrix.M21:F2},{matrix.M22:F2} / {matrix.M31:F2},{matrix.M32:F2}]");

// Build 'position' in the current element coordinate space
Expand All @@ -423,6 +425,7 @@ private static (UIElement? element, Branch? stale) SearchDownForTopMostElementAt
element.ApplyRenderTransform(ref matrix, ignoreOrigin: true);
matrix.Translation = default; //
element.ApplyElementCustomTransform(ref matrix);
element.ApplyFlowDirectionTransform(ref matrix);
matrix = matrix.Inverse();

// The maximum region where the current element and its children might draw themselves
Expand Down
16 changes: 16 additions & 0 deletions src/Uno.UI/UI/Xaml/UIElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ internal static Matrix3x2 GetTransform(UIElement from, UIElement to)
elt.ApplyRenderTransform(ref matrix);
elt.ApplyLayoutTransform(ref matrix);
elt.ApplyElementCustomTransform(ref matrix);
elt.ApplyFlowDirectionTransform(ref matrix);
} while (elt.TryGetParentUIElementForTransformToVisual(out elt, ref matrix) && elt != to); // If possible we stop as soon as we reach 'to'

if (to is not null && elt != to)
Expand Down Expand Up @@ -591,6 +592,21 @@ internal void ApplyElementCustomTransform(ref Matrix3x2 matrix)
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ApplyFlowDirectionTransform(ref Matrix3x2 matrix)
{
#if SUPPORTS_RTL
if (this is FrameworkElement fe && VisualTreeHelper.GetParent(this) is FrameworkElement parent)
{
if (fe.FlowDirection != parent.FlowDirection)
{
matrix *= Matrix3x2.CreateScale(-1.0f, 1.0f);
matrix *= Matrix3x2.CreateTranslation((float)parent.RenderSize.Width, 0);
}
}
#endif
}

#if !__IOS__ && !__ANDROID__ && !__MACOS__ // This is the default implementation, but it can be customized per platform
/// <summary>
/// Note: Offsets are only an approximation that does not take into consideration possible transformations
Expand Down
21 changes: 20 additions & 1 deletion src/Uno.UI/UI/Xaml/UIElement.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Uno.UI.Xaml.Core;
using Uno.UI.DataBinding;
using Uno.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;

namespace Windows.UI.Xaml
Expand Down Expand Up @@ -269,7 +270,8 @@ internal void ArrangeVisual(Rect finalRect, Rect? clippedFrame = default)

var oldClip = oldClippedFrame;
var newClip = clippedFrame;
if (oldRect != newRect || oldClip != newClip)

if (oldRect != newRect || oldClip != newClip || Visual.TransformMatrix != GetFlowDirectionTransform())
{
if (
newRect.Width < 0
Expand Down Expand Up @@ -305,6 +307,22 @@ internal void ArrangeVisual(Rect finalRect, Rect? clippedFrame = default)
}
}

private Matrix4x4 GetFlowDirectionTransform()
=> ShouldMirrorVisual() ? new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, (float)RenderSize.Width, 0.0f)) : Matrix4x4.Identity;

private bool ShouldMirrorVisual()
{
if (this is FrameworkElement fe && this.FindFirstParent<FrameworkElement>(includeCurrent: false) is FrameworkElement feParent)
{
if (fe.FlowDirection != feParent.FlowDirection)
{
return true;
}
}

return false;
}

internal virtual void OnArrangeVisual(Rect rect, Rect? clip)
{
var roundedRect = LayoutRound(rect);
Expand All @@ -313,6 +331,7 @@ internal virtual void OnArrangeVisual(Rect rect, Rect? clip)
visual.Offset = new Vector3((float)roundedRect.X, (float)roundedRect.Y, 0) + _translation;
visual.Size = new Vector2((float)roundedRect.Width, (float)roundedRect.Height);
visual.CenterPoint = new Vector3((float)RenderTransformOrigin.X, (float)RenderTransformOrigin.Y, 0);
Visual.TransformMatrix = GetFlowDirectionTransform();

// The clipping applied by our parent due to layout constraints are pushed to the visual through the ViewBox property
// This allows special handling of this clipping by the compositor (cf. ShapeVisual.Render).
Expand Down

0 comments on commit 912de33

Please sign in to comment.