Browse Source

Merge pull request #7810 from danielmayost/feature/flowDirectionImpl

Implement FlowDirection - Bidirectional layout
pull/7950/head
Max Katz 5 years ago
committed by GitHub
parent
commit
6e35970718
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      samples/ControlCatalog/MainView.xaml
  2. 9
      samples/ControlCatalog/MainView.xaml.cs
  3. 2
      samples/ControlCatalog/Pages/ScreenPage.cs
  4. 19
      src/Avalonia.Controls/ComboBox.cs
  5. 68
      src/Avalonia.Controls/Control.cs
  6. 2
      src/Avalonia.Controls/Image.cs
  7. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  8. 2
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  9. 30
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  10. 2
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  11. 2
      src/Avalonia.Controls/TextBlock.cs
  12. 6
      src/Avalonia.Controls/TopLevel.cs
  13. 1
      src/Avalonia.Themes.Default/Controls/CheckBox.xaml
  14. 1
      src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
  15. 4
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  16. 6
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  17. 6
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  18. 19
      src/Avalonia.Visuals/Visual.cs
  19. 6
      src/Avalonia.Visuals/VisualExtensions.cs
  20. 5
      src/Avalonia.Visuals/VisualTree/IVisual.cs

8
samples/ControlCatalog/MainView.xaml

@ -190,6 +190,14 @@
<WindowTransparencyLevel>Mica</WindowTransparencyLevel>
</ComboBox.Items>
</ComboBox>
<ComboBox x:Name="FlowDirection"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<ComboBox.Items>
<FlowDirection>LeftToRight</FlowDirection>
<FlowDirection>RightToLeft</FlowDirection>
</ComboBox.Items>
</ComboBox>
<ComboBox HorizontalAlignment="Stretch"
Items="{Binding WindowStates}"
SelectedItem="{Binding WindowState}" />

9
samples/ControlCatalog/MainView.xaml.cs

@ -76,6 +76,15 @@ namespace ControlCatalog
}
};
var flowDirections = this.Find<ComboBox>("FlowDirection");
flowDirections.SelectionChanged += (sender, e) =>
{
if (flowDirections.SelectedItem is FlowDirection flowDirection)
{
this.FlowDirection = flowDirection;
}
};
var decorations = this.Find<ComboBox>("Decorations");
decorations.SelectionChanged += (sender, e) =>
{

2
samples/ControlCatalog/Pages/ScreenPage.cs

@ -13,6 +13,8 @@ namespace ControlCatalog.Pages
{
private double _leftMost;
protected override bool BypassFlowDirectionPolicies => true;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);

19
src/Avalonia.Controls/ComboBox.cs

@ -184,6 +184,25 @@ namespace Avalonia.Controls
this.UpdateSelectionBoxItem(SelectedItem);
}
// Because the SelectedItem isn't connected to the visual tree
public override void InvalidateMirrorTransform()
{
base.InvalidateMirrorTransform();
if (SelectedItem is Control selectedControl)
{
selectedControl.InvalidateMirrorTransform();
foreach (var visual in selectedControl.GetVisualDescendants())
{
if (visual is Control childControl)
{
childControl.InvalidateMirrorTransform();
}
}
}
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{

68
src/Avalonia.Controls/Control.cs

@ -160,6 +160,16 @@ namespace Avalonia.Controls
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <summary>
/// Gets a value indicating whether control bypass FlowDirecton policies.
/// </summary>
/// <remarks>
/// Related to FlowDirection system and returns false as default, so if
/// <see cref="FlowDirection"/> is RTL then control will get a mirror presentation.
/// For controls that want to avoid this behavior, override this property and return true.
/// </remarks>
protected virtual bool BypassFlowDirectionPolicies => false;
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter)
{
@ -219,6 +229,14 @@ namespace Avalonia.Controls
base.OnDetachedFromVisualTreeCore(e);
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
InvalidateMirrorTransform();
}
/// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e)
{
@ -329,5 +347,55 @@ namespace Avalonia.Controls
}
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == FlowDirectionProperty)
{
InvalidateMirrorTransform();
foreach (var visual in VisualChildren)
{
if (visual is Control child)
{
child.InvalidateMirrorTransform();
}
}
}
}
/// <summary>
/// Computes the <see cref="IVisual.HasMirrorTransform"/> value according to the
/// <see cref="FlowDirection"/> and <see cref="BypassFlowDirectionPolicies"/>
/// </summary>
public virtual void InvalidateMirrorTransform()
{
var flowDirection = this.FlowDirection;
var parentFlowDirection = FlowDirection.LeftToRight;
bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies;
bool parentBypassFlowDirectionPolicies = false;
var parent = this.FindAncestorOfType<Control>();
if (parent != null)
{
parentFlowDirection = parent.FlowDirection;
parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies;
}
else if (Parent is Control logicalParent)
{
parentFlowDirection = logicalParent.FlowDirection;
parentBypassFlowDirectionPolicies = logicalParent.BypassFlowDirectionPolicies;
}
bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies;
bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies;
bool shouldApplyMirrorTransform = thisShouldBeMirrored != parentShouldBeMirrored;
HasMirrorTransform = shouldApplyMirrorTransform;
}
}
}

2
src/Avalonia.Controls/Image.cs

@ -66,6 +66,8 @@ namespace Avalonia.Controls
set { SetValue(StretchDirectionProperty, value); }
}
protected override bool BypassFlowDirectionPolicies => true;
/// <summary>
/// Renders the control.
/// </summary>

2
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -282,6 +282,8 @@ namespace Avalonia.Controls.Presenters
}
}
protected override bool BypassFlowDirectionPolicies => true;
/// <summary>
/// Creates the <see cref="TextLayout"/> used to render the text.
/// </summary>

2
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -76,7 +76,7 @@ namespace Avalonia.Controls.Primitives
Rect? rect = null)
{
_positionerParameters.ConfigurePosition((TopLevel)_overlayLayer.GetVisualRoot()!, target, placement, offset, anchor,
gravity, constraintAdjustment, rect);
gravity, constraintAdjustment, rect, FlowDirection);
UpdatePosition();
}

30
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -46,6 +46,7 @@ Copyright © 2019 Nikita Tsukanov
using System;
using Avalonia.VisualTree;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives.PopupPositioning
{
@ -444,7 +445,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
TopLevel topLevel,
IVisual target, PlacementMode placement, Point offset,
PopupAnchor anchor, PopupGravity gravity,
PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect)
PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect,
FlowDirection flowDirection)
{
// We need a better way for tracking the last pointer position
#pragma warning disable CS0618 // Type or member is obsolete
@ -503,6 +505,32 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
else
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
}
// Invert coordinate system if FlowDirection is RTL
if (flowDirection == FlowDirection.RightToLeft)
{
if ((positionerParameters.Anchor & PopupAnchor.Right) == PopupAnchor.Right)
{
positionerParameters.Anchor ^= PopupAnchor.Right;
positionerParameters.Anchor |= PopupAnchor.Left;
}
else if ((positionerParameters.Anchor & PopupAnchor.Left) == PopupAnchor.Left)
{
positionerParameters.Anchor ^= PopupAnchor.Left;
positionerParameters.Anchor |= PopupAnchor.Right;
}
if ((positionerParameters.Gravity & PopupGravity.Right) == PopupGravity.Right)
{
positionerParameters.Gravity ^= PopupGravity.Right;
positionerParameters.Gravity |= PopupGravity.Left;
}
else if ((positionerParameters.Gravity & PopupGravity.Left) == PopupGravity.Left)
{
positionerParameters.Gravity ^= PopupGravity.Left;
positionerParameters.Gravity |= PopupGravity.Right;
}
}
}
}

2
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -93,7 +93,7 @@ namespace Avalonia.Controls.Primitives
Rect? rect = null)
{
_positionerParameters.ConfigurePosition(ParentTopLevel, target,
placement, offset, anchor, gravity, constraintAdjustment, rect);
placement, offset, anchor, gravity, constraintAdjustment, rect, FlowDirection);
if (_positionerParameters.Size != default)
UpdatePosition();

2
src/Avalonia.Controls/TextBlock.cs

@ -318,6 +318,8 @@ namespace Avalonia.Controls
set => SetValue(TextDecorationsProperty, value);
}
protected override bool BypassFlowDirectionPolicies => true;
/// <summary>
/// The BaselineOffset property provides an adjustment to baseline offset
/// </summary>

6
src/Avalonia.Controls/TopLevel.cs

@ -350,6 +350,12 @@ namespace Avalonia.Controls
/// </summary>
protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this);
public override void InvalidateMirrorTransform()
{
}
protected override bool BypassFlowDirectionPolicies => true;
/// <summary>
/// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary>

1
src/Avalonia.Themes.Default/Controls/CheckBox.xaml

@ -26,6 +26,7 @@
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FlowDirection="LeftToRight"
Data="M 1145.607177734375,430 C1145.607177734375,430 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1138,434.5538330078125 1138,434.5538330078125 1138,434.5538330078125 1141.482177734375,438 1141.482177734375,438 1141.482177734375,438 1141.96875,437.9375 1141.96875,437.9375 1141.96875,437.9375 1147,431.34619140625 1147,431.34619140625 1147,431.34619140625 1145.607177734375,430 1145.607177734375,430 z"/>
<Rectangle Name="indeterminateMark"
Fill="{DynamicResource HighlightBrush}"

1
src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

@ -152,6 +152,7 @@
<Setter Property="Data" Value="M1507 31L438 1101L-119 543L-29 453L438 919L1417 -59L1507 31Z" />
<Setter Property="Width" Value="9" />
<Setter Property="Opacity" Value="1" />
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
<!-- Checked PointerOver State -->

4
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -182,5 +182,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.TextFo
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable<System.Int32>)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.VisualTree.IVisual.HasMirrorTransform' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.VisualTree.IVisual.HasMirrorTransform.get()' is present in the implementation but not in the contract.
MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice<T>..ctor(System.ReadOnlyMemory<T>, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract.
Total Issues: 184
Total Issues: 186

6
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -285,6 +285,12 @@ namespace Avalonia.Rendering
renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
}
if (visual.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
renderTransform *= mirrorMatrix;
}
m = renderTransform * m;
if (clipToBounds)

6
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -195,6 +195,12 @@ namespace Avalonia.Rendering.SceneGraph
renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
}
if (visual.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
renderTransform *= mirrorMatrix;
}
m = renderTransform * m;
using (contextImpl.BeginUpdate(node))

19
src/Avalonia.Visuals/Visual.cs

@ -68,6 +68,12 @@ namespace Avalonia
public static readonly StyledProperty<IBrush?> OpacityMaskProperty =
AvaloniaProperty.Register<Visual, IBrush?>(nameof(OpacityMask));
/// <summary>
/// Defines the <see cref="HasMirrorTransform"/> property.
/// </summary>
public static readonly DirectProperty<Visual, bool> HasMirrorTransformProperty =
AvaloniaProperty.RegisterDirect<Visual, bool>(nameof(HasMirrorTransform), o => o.HasMirrorTransform);
/// <summary>
/// Defines the <see cref="RenderTransform"/> property.
/// </summary>
@ -96,6 +102,7 @@ namespace Avalonia
private TransformedBounds? _transformedBounds;
private IRenderRoot? _visualRoot;
private IVisual? _visualParent;
private bool _hasMirrorTransform;
/// <summary>
/// Initializes static members of the <see cref="Visual"/> class.
@ -107,7 +114,8 @@ namespace Avalonia
ClipProperty,
ClipToBoundsProperty,
IsVisibleProperty,
OpacityProperty);
OpacityProperty,
HasMirrorTransformProperty);
RenderTransformProperty.Changed.Subscribe(RenderTransformChanged);
ZIndexProperty.Changed.Subscribe(ZIndexChanged);
}
@ -219,6 +227,15 @@ namespace Avalonia
set { SetValue(OpacityMaskProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether to apply mirror transform on this control.
/// </summary>
public bool HasMirrorTransform
{
get { return _hasMirrorTransform; }
protected set { SetAndRaise(HasMirrorTransformProperty, ref _hasMirrorTransform, value); }
}
/// <summary>
/// Gets or sets the render transform of the control.
/// </summary>

6
src/Avalonia.Visuals/VisualExtensions.cs

@ -110,6 +110,12 @@ namespace Avalonia
result *= renderTransform;
}
if (v.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, v.Bounds.Width, 0);
result *= mirrorMatrix;
}
var topLeft = v.Bounds.TopLeft;
if (topLeft != default)

5
src/Avalonia.Visuals/VisualTree/IVisual.cs

@ -75,6 +75,11 @@ namespace Avalonia.VisualTree
/// </summary>
IBrush? OpacityMask { get; set; }
/// <summary>
/// Gets a value indicating whether to apply mirror transform on this control.
/// </summary>
bool HasMirrorTransform { get; }
/// <summary>
/// Gets or sets the render transform of the control.
/// </summary>

Loading…
Cancel
Save