Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

[All] Basic Right-To-Left Support #1222

Merged
merged 15 commits into from
Nov 9, 2017
Merged

[All] Basic Right-To-Left Support #1222

merged 15 commits into from
Nov 9, 2017

Conversation

samhouts
Copy link
Member

@samhouts samhouts commented Oct 24, 2017

Description of Change

Adds support for right-to-left layouts.

As with UWP, respecting the device's layout direction based on the selected language/locale is the developer's explicit choice and does not happen automatically. Set the FlowDirection of the root View/Page to the Device.FlowDirection value. All other child views with MatchParent will inherit this value.

All Views default to the MatchParent FlowDirection. A View without a Parent will default to LeftToRight, and this value will be recursively passed down to the View's children, unless they have an explicitly set FlowDirection of their own.

Renderers can access the IVisualElementController.EffectiveFlowDirection to get the actual specified direction for a View/Page and whether the value was inherited (implicit) or explicitly set.

Note that you should set the FlowDirection once on initial layout. Changing this value causes an expensive relayout and will affect performance.

Known Limitations

General

  • NavigationPage button location, toolbar item location, and transition animation is currently controlled by the device locale, not the FlowDirection setting.
  • CarouselPage swipe direction does not flip.
  • Images need to mirror when they are explicitly set to right-to-left. May also need to support asset suffixes.
  • TextDirection property needs to be added.
  • Global app setting for FlowDirection

Android

  • Searchbar orientation is controlled by the device locale, not the FlowDirection setting.
  • ContextAction placement is controlled by the device locale, not the FlowDirection setting.

iOS

  • Stepper orientation is controlled by the device locale, not the FlowDirection setting.
  • EntryCell text alignment is controlled by the device locale, not the FlowDirection setting.
  • ContextAction gesture and alignment are not flipped.

UWP

macOS

  • MasterDetailPage Master page does not pop out from the right in RTL
  • Text alignment is controlled by the device locale, not the FlowDirection setting.
  • ProgressBar is not controlled by the FlowDirection setting.
  • ImageCell, SwitchCell, TextCell are not controlled by the FlowDirection setting.
  • Searchbar orientation is controlled by the device locale, not the FlowDirection setting.

Testing

Android

Users will need to set android:supportsRtl="true" and set Android's MinSDKVersion to 17 or higher.

To test device-specified RTL on Android, either select a language that is RTL or enable Force RTL layout direction in the Developer Options. You will also need to add your language to the list of languages your app supports in the Info.plist. See https://developer.xamarin.com/guides/ios/advanced_topics/localization_and_internationalization/ for more information. If you do change your language and region, you may encounter an exception with DatePickers. See Bug 59077 for more information and a workaround.

iOS

There's no easy way to test device-specified RTL on Xamarin.iOS. The pseudolanguage options are Xcode only. You will need to change the language and region to an RTL locale in order to test it. Please note that once you do so, DatePickers will throw an exception if you do not include the resources required for alternate calendars. For example, if you're testing in Arabic, be sure to select mideast in the iOSBuild Internationalization section.

The ability to force a control to flip directions was added in iOS 9, so iOS 8 will not be fully supported.

UWP

Similar to iOS, you will need to change the language and region to an RTL locale in order to test device-specified RTL. You will also need to add the language resource to your Package.appxmanifest for your app to support it.

Visual comparisons (you asked for it, get ready)

Android

Before
android-before

After (LTR)
android-after-ltr

After (RTL Forced)
android-after-rtl

After (RTL Locale)
android-after-rtl-locale

iOS

Before
ios-before

After (LTR)
ios-after-ltr

After (RTL Forced)
ios-after-rtl

After (RTL Locale)
ios-after-rtl-locale

UWP

Before
uwp-before

After (LTR)
uwp-after-ltr

After (RTL Forced)
uwp-after-rtl

After (RTL Locale)
uwp-atfer-rtl-locale

Bugs Fixed

  • N/A

API Changes

Added:

  • enum EffectiveFlowDirection
  • enum FlowDirection
  • IVisualElementController.EffectiveFlowDirection
  • VisualElement.FlowDirection
  • Device.FlowDirection

Behavioral Changes

Android

  • For API >=17, we now set the TextAlignment instead of the Gravity of the EditText when we change the HorizontalTextAlignment property.

PR Checklist

  • Has tests (manual!)
  • Rebased on top of master at time of PR
  • Changes adhere to coding standard
  • Consolidate commits as makes sense

@samhouts samhouts added cla-not-required DO-NOT-MERGE-!!! 🛑 This is in progress and needs to be updated before it can be merged. and removed cla-required labels Oct 24, 2017
@maamountki
Copy link

maamountki commented Oct 24, 2017

In UWP, Unlike entry behavior, Editor text is right-aligned if text already have rtl language value regardless of the selected language/locale, Also if keyboard changed to rtl language while editor text is empty, Editor will align text to right as you typing.

@rmarinho
Copy link
Member

Needs docs update

@samhouts
Copy link
Member Author

@maamountki Yes, sorry, what I meant to say is that I am unable to force the text alignment, but it does properly align itself with the device language/locale. Thank you!

@samhouts samhouts removed the DO-NOT-MERGE-!!! 🛑 This is in progress and needs to be updated before it can be merged. label Oct 25, 2017
public enum FlowDirection
{
MatchParent = 0,
LeftToRight = 1,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation screwy (my fault)


_effectiveFlowDirection = value;
InvalidateMeasureInternal(InvalidationTrigger.Undefined);
OnPropertyChanged(FlowDirectionProperty.PropertyName);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may have some negative impacts on anyone binding this property. I cant imagine why anyone would be TwoWay binding this, but ya know.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one isn't bindable, @jassmith. I don't get the comment

if (VisualElementController == null || NativeView == null)
return;

if (VisualElementController.EffectiveFlowDirection.HasFlag(EffectiveFlowDirection.RightToLeft))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HasFlag is super evil and slow. Like really REALLY slow. Don't use HasFlag.

@jassmith
Copy link

Approved pending tests and fixing HasFlag

Copy link
Member

@StephaneDelcroix StephaneDelcroix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the Core part, and wow, this is impressive! I left a few comments/questions but I like this.


public class FlowDirectionGalleryLandingPage : ContentPage
{
FlowDirection DeviceDirection => Device.Info.CurrentFlowDirection;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is Device.Info.CurrentFlowDirection the recommended way to access the FlowDirection?

if so, it causes 2 problems:

  • Device.Info is attributed with [EditorBrowsable(EditorBrowsableState.Never)]
  • it makes it hard to access from a XAML page. the FlowDirection should probably be exposed at the Device level, so one can do <ContentPage FlowDirection="{x:Static Device.FlowDirection}" ... /> (x:Static syntax is Type.Member and doesn't support Type.Member.Member)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it. Trying it out now...

@@ -605,7 +604,8 @@ enum BindableContextAttributes
IsBeingSet = 1 << 1,
IsDynamicResource = 1 << 2,
IsSetFromStyle = 1 << 3,
IsDefaultValue = 1 << 4
IsDefaultValue = 1 << 4,
IsInherited = 1 << 5,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we support Inherited BPs ? I don't see this flag used anywhere

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry thats leftover from me when I was doing something else with this

@@ -18,6 +19,24 @@ public abstract class Cell : Element, ICellController

bool _nextCallToForceUpdateSizeQueued;

EffectiveFlowDirection _effectiveFlowDirection = EffectiveFlowDirection.LeftToRight | EffectiveFlowDirection.Implicit;
EffectiveFlowDirection IFlowDirectionController.EffectiveFlowDirection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as you're implementing this explicitly, I guess this is not meant to be set directly by the user, so there's no point in making this property bindable ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct

OnPropertyChanged(VisualElement.FlowDirectionProperty.PropertyName);
}
}
IFlowDirectionController FlowController => this;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style Police here. You're missing a white line. Please pull over !

(well, fix it only if you have other things to push)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👮

OnPropertyChanged(VisualElement.FlowDirectionProperty.PropertyName);
}
}
IFlowDirectionController FlowController => this;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I understand this right, you added this property so you don't have to cast to IFlowDirectionControllereverytime you want to invoke an explicitly implemented interface method ? If so, make it a field

IFlowDirectionController _flowController = this;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't set a field to this.

return EffectiveFlowDirection.RightToLeft | mode;
}

throw new InvalidOperationException($"Cannot convert {self} to {nameof(EffectiveFlowDirection)}.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean default:

[EditorBrowsable(EditorBrowsableState.Never)]
public static bool IsRightToLeft(this EffectiveFlowDirection self)
{
return (self & EffectiveFlowDirection.RightToLeft) != 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the canonical way to check a flag is (self & EffectiveFlowDirection.RightToLeft) == EffectiveFlowDirection.RightToLeft

it's not important when the flag has only a single bit set, but otherwise, it is

{
isRightToLeft = parent.EffectiveFlowDirection.IsRightToLeft();
if (isRightToLeft)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd reduce nesting

if (parent != null && (isRightToLeft = parent.EffectiveFlowDirection.IsRightToLeft()))
    region = new Rectangle(parent.Width - region.Right, region.Y, region.Width, region.Height);


_effectiveFlowDirection = value;
InvalidateMeasureInternal(InvalidationTrigger.Undefined);
OnPropertyChanged(FlowDirectionProperty.PropertyName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one isn't bindable, @jassmith. I don't get the comment

namespace Xamarin.Forms
{
[Flags]
public enum EffectiveFlowDirection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of the implementation hide EffectiveFlowDirection through explicit interface implementation, so I guess this isn't mean to be used by our users at all ? if so, attribute the type or move it to the Internals namespace

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should only be set by Core code (users set FlowDirection), but it should be exposed via IVisualElementController so renderers can use it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API only used by renderers is usually attributed, or in the internals namespace, or both

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but our users will want to access this for their custom renderers, so it feels like it should be exposed.

@samhouts samhouts changed the title Basic Right-To-Left Support [All] Basic Right-To-Left Support Oct 27, 2017
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="AndroidControlGallery.AndroidControlGallery" android:installLocation="auto">
<uses-sdk android:minSdkVersion="15" />
<uses-sdk android:minSdkVersion="17" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this prevent us from testing things for API 15/16 in the ControlGallery?

@@ -58,6 +63,17 @@ void UpdateHeight()
_view.SetRenderHeight(Cell.RenderHeight);
}

void UpdateFlowDirection()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This same UpdateFlowDirection code is repeated several times - seems like a candidate for an extension method.

@@ -27,6 +27,7 @@ public static class Device
public static void SetIdiom(TargetIdiom value) => Idiom = value;
public static TargetIdiom Idiom { get; internal set; }

//TODO: Why are there two of these? This is never used...?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♂️

public enum EffectiveFlowDirection
{
LeftToRight = 1 << 0,
RightToLeft = 1 << 1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"It's never used by users" seriously contradicts the "needs to be public for custom renderers". Please fix as this is public API

Copy link
Contributor

@kingces95 kingces95 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive! Most impressive!

Control.FlowDirection = WFlowDirection.RightToLeft;
else if (VisualElementController.EffectiveFlowDirection.IsLeftToRight())
Control.FlowDirection = WFlowDirection.LeftToRight;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not ur fault but it's a shame our conventions lead us to repeat this code in so many places...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conventions don't force the repetition of the stuff inside UpdateFlowDirection - all of that could be moved into an extension method.

@mhmd-azeez
Copy link

@samhouts #2447 #2448 #2449. When do you expect you guys will be able to work on the current limitations (listed in the PR)?

@ali-h2010
Copy link

ali-h2010 commented Apr 15, 2018

@Encrypt0r Your post is much more descriptive than mine. Could you please create a new issue with the same content?
Wait you already did in #2447
Many thanks

@smartprogrammer93
Copy link

@samhouts
FlowDirection = rtl on horizental scrollview breaks the scrolling in it

@samhouts
Copy link
Member Author

samhouts commented Jun 9, 2018

That's not good! Would you mind opening an issue? Thanks!

@smartprogrammer93
Copy link

@samhouts
done #3000

@ali-h2010
Copy link

ali-h2010 commented Jun 14, 2018

Was anyone able to bind the FlowDirection using ViewModels?
I tried but there is no response in the XAML

@ali-h2010
Copy link

ali-h2010 commented Jun 15, 2018

@samhouts
I am also was not able to use FormattedString in a label. This ignores the FlowDirection

<Label TextColor="Black" FlowDirection="MatchParent"> <Label.FormattedText> <FormattedString> <Span Text="{Translate:TranslateExtension Text=OldPassword}" /> <Span Text="*" ForegroundColor="#CD4A4A" FontAttributes="Bold" /> </FormattedString> </Label.FormattedText> </Label>

@samhouts
Copy link
Member Author

@ali-h2010 Very sorry to have missed your comment! Please open new issues for any problems you encounter so we don't lose them in this thread. Thank you!

@PureWeen
Copy link
Contributor

@ali-h2010 check my notes here as well to see if that resolves the issue for you
#3311

@rmarinho rmarinho mentioned this pull request Aug 8, 2018
3 tasks
@mhmd-azeez mhmd-azeez mentioned this pull request Sep 18, 2018
26 tasks
@samhouts samhouts modified the milestones: 3.0.0, 2.5.0 Aug 23, 2019
PureWeen pushed a commit that referenced this pull request Oct 24, 2019
* Restart RTL work

* Remove IsInherited flag as it never got used

* [Core] Unit tests

* [Core] FlowDirection

* Add FlowDirectionGallery

* Android gallery supports RTL

Need to set minSdkVersion to 17 to test

* iOS gallery supports RTL

* UWP gallery supports RTL

* [Android] Implement FlowDirection

* [iOS] Implement FlowDirection

* [macOS] Implement FlowDirection

* [UWP] Implement FlowDirection

* Update docs

* [Core] Simplify EffectiveFlowDirection enum & expose helper extensions

Also, TEST TEST TEST

* Update docs
@samhouts samhouts modified the milestones: 3.0.0, 2.5.0 Oct 29, 2019
@huangjinshe
Copy link

In the newest version I download today. I confirm the problem is very different.

The Label flow direction totally based on the content.
Which mean If you set content to : "English", it will always show as: Left to right.
If you set content to some right to left version font such like arabic: "سىناق". It will always show as Right to left.

Which mean it will totally based on the font, and Android or Xamarin.Foms auto decide that,
I really don't like it, it will change the default layout which we already design.(I want it always Left to right, when I wosn't set the flow direction of Label)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a/rtl API-change Heads-up to reviewers that this PR may contain an API change breaking Changes behavior or appearance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet