Right to Left
A guide about QtQuick tools that help adapting UI to Right-to-Left (RTL) locales like Arabic and Hebrew.
How to detect an RTL environment in QtQuick
QtQml, QtQuick and QtQuick.Controls.2 provide us with three ways to check for RTL, each on a different level of granularity:
Qt.application.layoutDirectionLayoutMirroring.enabledattached propertyControl::mirrored&Popup::mirrored
Global
Qt detects current locate at the application level as a whole, sets the layout direction of QGuiApplication accordingly, and exposes it as a property through the Qt global object:
Qt.application.layoutDirection === Qt.RightToLeft
Hierarchy tree of visual items
Any Item and Window component may have a LayoutMirroring object attached to override the implicitly inherited layout direction.
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
Normally you want to always set childrenInherit: true unless you really know what you are doing.
The default value for LayoutMirroring.enabled is not bound in any way to Qt.application.layoutDirection, and must be set on each root Window of your application explicitly if you mean to support RTL layouts.
If you are writing a root content item for an externally provided Window (such as when a window is a custom subclass created in C++), binding LayoutMirroring on the content item is not enough: QQC2.Overlay.overlay will be a sibling to your content item, thus it won't inherit the mirroring, and you'll end up with weird non-adapted menus, dialogs and other popups. Use this trick:
Item {
id: customWindowContentItem
Window.onWindowChanged: {
if (Window.window) {
Window.window.LayoutMirroring.enabled = Qt.binding(() => Qt.application.layoutDirection === Qt.RightToLeft);
Window.window.LayoutMirroring.childrenInherit = true;
}
}
}
Controls
QtQuick.Controls module provides its own shorthand: Control::mirrored & Popup::mirrored read-only boolean properties. And these are the most fine-grained ones you can rely on.
Interestingly enough, they are not mentioned on the Right-to-left User Interfaces Qt documentation page, because QQC2 is considered a separate downstream module, so you can't refer to QQC2 types there. They do share the same underlying infrastructure though. In fact, all this is implemented in the base QQuickItem class, and mirrored is basically identical to LayoutMirroring.enabled — so it won't be unreasonable to wonder why do you need to allocate memory for extra attached objects or expose the value only in subclasses? But this was not always the case: up until Qt 6.2 they took Control::locale into account as well.
Technically, you could still bind mirrored to locale.textDirection == Qt.RightToLeft to somewhat replicate the old behavior, although personally I've never seen such code or felt the need for it.
Standard library
Now that we know how to detect an RTL layout direction, let's learn how can we use this information to properly reverse our UI. Some things are automatically affected by LayoutMirroring.enabled but other aren't.
✅ Affected:
Item::anchors:left,right,leftMargin,rightMargin&horizontalCenterOffset.- QtQuick Positioners:
effectiveLayoutDirectionproperty ofGrid,Flow,Row&Column. QtQuick.Layouts: default horizontal alignment forColumnLayoutand default horizontal flow (so calledeffectiveLayoutDirection, except this time it's not publicly exposed) forRowLayoutandGridLayout.Text/Label,TextInput/TextFieldandTextEdit/TextAreacounterparts, but only if thehorizontalAlignmentis explicitly set.
❌ Not affected:
- Let's get this elephant out of the room first. The
Item::xcoordinate property is not mirrored. To specify the distance between a leading edge and its parent useparent.width - width - xexpression. This trick is useful for, e.g. positioning Menu popup around its visual parent drop-down button. - Paddings (leftPadding & rightPadding), pretty much everywhere, even in types like QtQuick Positioners which are otherwise responsive to RTL:
- Margins of
Flickable(and, by extension,ListView), even though internally they are confusingly dubbed asd->hData.startMargin&endMargin. - Effective default alignment of
Text. That's why in KDE codebase there is a workaround inQQC2.Labelimplementations (one, two):T.Label {
// Work around Qt bug where left aligned text is not right aligned
// in RTL mode unless horizontalAlignment is explicitly set.
// https://bugreports.qt.io/browse/QTBUG-95873
horizontalAlignment: Text.AlignLeft
}
For paddings and margins which are not automatically mirrored you have to write conditional code with repetitive expressions like this:
leftPadding: mirrored ? Kirigami.Units.smallSpacing : 0
rightPadding: mirrored ? 0 : Kirigami.Units.smallSpacing
This is pretty much how QQC2 controls are defined. When expressions get too long to reasonably repeat them more than once, consider declaring a pair of semantically named properties:
// optionally, mark them as `readonly`
property real leadingPadding: 0
property real trailingPadding: Kirigami.Units.smallSpacing
leftPadding: mirrored ? trailingPadding : leadingPadding
rightPadding: mirrored ? leadingPadding : trailingPadding
How to use
When implementing QQC2 Controls, such as QQC2 style, use control's mirrored property for all horizontal positioning. If the root component is not a sub-type of QQC2.Control, make sure to check for root component's root.LayoutMirroring.enabled instead.
Do not check for any child component's LayoutMirroring.enabled! Only root component's attached object is valid for mirroring, because LayoutMirroring.childrenInherit might be disabled, in which case the high-level children would not inherit the mirroring but the control's internals should still be mirrored; in other words, implementation details does not count as "children".
Icons
The Qt documentation suggests using Image { mirror: true } for images, however not all images can be simply mirrored in RTL (see Apple Developer Documentation), there is no automatic icon reversal provisioned in Free Desktop icon themes specification, and there is no mirror group property for QtQuickIcon gadget (also known as AbstractButton::icon and Action::icon).
Free Desktop icon themes specification, however, does specify a fallback chain mechanism whereby a requested icon name consists of parts from most generic to most specific, joined with dashes. A missing icon would be searched again by its name without the last (most specific) part. Thus, developers may request an RTL variant of an icon, and it would automatically fallback to non-RTL icon if RTL variant does not exist.
Use the following idiom to request an adaptive icon for controls:
Button {
icon.name: mirrored ? 'some-icon-rtl' : 'some-icon'
}
For non-controls, use LayoutMirroring.enabled instead of mirrored.
Icons can also have symbolic counterparts, which is a colorless/monochrome variant suitable for small and contrast contexts, such as buttons and menu items. For such icon sets RTL should take priority, because it is better to fallback to a non-symbolic RTL icon rather than to a symbolic but not mirrored.
For symbolic RTL icons use *-rtl-symbolic naming scheme.
How to test
Running a Qt app with --reverse flag would only force the default layout direction, but won't localize text labels, so the overall experience will be limited.
To enable full experience, you need to set language/locale environment variables, for example:
export LANGUAGE=ar:en_US LANG=ar_EG.UTF-8 LC_ALL=
# or save aliases to your shell rc
alias RTL="LANGUAGE=ar:en_US LANG=ar_EG.UTF-8 LC_ALL="
alias RTL_="export LANGUAGE=ar:en_US LANG=ar_EG.UTF-8 LC_ALL="
RTL dolphin
Resources
- Qt documentation: Right-to-left User Interfaces
- Apple Developer Documentation