diff --git a/change/react-native-windows-2020-04-27-16-11-30-allowRTL.json b/change/react-native-windows-2020-04-27-16-11-30-allowRTL.json new file mode 100644 index 00000000000..b45d80d551f --- /dev/null +++ b/change/react-native-windows-2020-04-27-16-11-30-allowRTL.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "auto-detect RTL and push into root view", + "packageName": "react-native-windows", + "email": "kmelmon@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-04-27T23:11:30.614Z" +} diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index d365e4d2c18..d759d30a997 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -155,7 +155,7 @@ void ReactInstanceWin::Initialize() noexcept { strongThis->m_deviceInfo = std::make_shared(legacyInstance); strongThis->m_appTheme = std::make_shared(legacyInstance, strongThis->m_uiMessageThread.LoadWithLock()); - strongThis->m_i18nInfo = react::uwp::I18nModule::GetI18nInfo(); + react::uwp::I18nHelper().Instance().setInfo(react::uwp::I18nModule::GetI18nInfo()); strongThis->m_appearanceListener = Mso::Make(legacyInstance); } }) @@ -200,7 +200,6 @@ void ReactInstanceWin::Initialize() noexcept { m_batchingUIThread, m_uiMessageThread.Load(), std::move(m_deviceInfo), - std::move(m_i18nInfo), std::move(m_appState), std::move(m_appTheme), std::move(m_appearanceListener), diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h index 0543bf8511d..717e4ded4eb 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h @@ -155,7 +155,6 @@ class ReactInstanceWin final : public Mso::ActiveObject m_appState; std::shared_ptr m_redboxHandler; std::shared_ptr m_appTheme; - std::pair m_i18nInfo{}; Mso::CntPtr m_appearanceListener; std::string m_bundleRootPath; }; diff --git a/vnext/ReactUWP/Base/CoreNativeModules.cpp b/vnext/ReactUWP/Base/CoreNativeModules.cpp index 4662a6609e1..6b2ce9e013d 100644 --- a/vnext/ReactUWP/Base/CoreNativeModules.cpp +++ b/vnext/ReactUWP/Base/CoreNativeModules.cpp @@ -53,7 +53,6 @@ std::vector GetCoreModules( const std::shared_ptr &messageQueue, const std::shared_ptr &uiMessageQueue, std::shared_ptr &&deviceInfo, - I18nModule::I18nInfo &&i18nInfo, std::shared_ptr &&appstate, std::shared_ptr &&appTheme, Mso::CntPtr &&appearanceListener, @@ -119,11 +118,7 @@ std::vector GetCoreModules( messageQueue); modules.emplace_back( - "I18nManager", - [i18nInfo = std::move(i18nInfo)]() mutable { - return createI18nModule(std::make_unique(std::move(i18nInfo))); - }, - messageQueue); + "I18nManager", []() mutable { return createI18nModule(std::make_unique()); }, messageQueue); modules.emplace_back( AppearanceModule::Name, diff --git a/vnext/ReactUWP/Base/CoreNativeModules.h b/vnext/ReactUWP/Base/CoreNativeModules.h index f5ef3861f65..d04ad83cbf6 100644 --- a/vnext/ReactUWP/Base/CoreNativeModules.h +++ b/vnext/ReactUWP/Base/CoreNativeModules.h @@ -29,7 +29,6 @@ std::vector GetCoreModules( const std::shared_ptr &messageQueue, const std::shared_ptr &uiMessageQueue, std::shared_ptr &&deviceInfo, - I18nModule::I18nInfo &&i18nInfo, std::shared_ptr &&appstate, std::shared_ptr &&appTheme, Mso::CntPtr &&appearanceListener, diff --git a/vnext/ReactUWP/Base/UwpReactInstance.cpp b/vnext/ReactUWP/Base/UwpReactInstance.cpp index b0361d0ef8a..8fbabcb20ce 100644 --- a/vnext/ReactUWP/Base/UwpReactInstance.cpp +++ b/vnext/ReactUWP/Base/UwpReactInstance.cpp @@ -114,7 +114,7 @@ void UwpReactInstance::Start(const std::shared_ptr &spThis, cons std::shared_ptr appstate = std::make_shared(spThis); std::shared_ptr appTheme = std::make_shared(spThis, m_defaultNativeThread); - std::pair i18nInfo = I18nModule::GetI18nInfo(); + I18nHelper::Instance().setInfo(I18nModule::GetI18nInfo()); auto appearanceListener = Mso::Make(spThis); // TODO: Figure out threading. What thread should this really be on? @@ -124,7 +124,6 @@ void UwpReactInstance::Start(const std::shared_ptr &spThis, cons spThis, deviceInfo, settings, - i18nInfo = std::move(i18nInfo), appstate = std::move(appstate), appTheme = std::move(appTheme), appearanceListener = std::move(appearanceListener)]() mutable { @@ -203,7 +202,6 @@ void UwpReactInstance::Start(const std::shared_ptr &spThis, cons m_batchingNativeThread, m_defaultNativeThread, std::move(deviceInfo), - std::move(i18nInfo), std::move(appstate), std::move(appTheme), std::move(appearanceListener), diff --git a/vnext/ReactUWP/Modules/I18nModule.cpp b/vnext/ReactUWP/Modules/I18nModule.cpp index 4b73f3f9bc2..b7ed1f7e6c7 100644 --- a/vnext/ReactUWP/Modules/I18nModule.cpp +++ b/vnext/ReactUWP/Modules/I18nModule.cpp @@ -30,14 +30,55 @@ namespace uwp { return std::make_pair(std::move(locale), std::move(isRTL)); } -I18nModule::I18nModule(std::pair &&i18nInfo) : m_i18nInfo(std::move(i18nInfo)) {} +I18nModule::I18nModule() : m_helper(I18nHelper::Instance()) {} -std::string I18nModule::getLocaleIdentifier() { +std::string I18nModule::getLocaleIdentifier() const { + return m_helper.getLocaleIdentifier(); +} + +bool I18nModule::getIsRTL() const { + return m_helper.getIsRTL(); +} + +void I18nModule::setAllowRTL(bool allowRTL) { + m_helper.setAllowRTL(allowRTL); +} + +void I18nModule::setForceRTL(bool forceRTL) { + m_helper.setForceRTL(forceRTL); +} + +/*static*/ I18nHelper &I18nHelper::Instance() { + static I18nHelper theInstance; + return theInstance; +} + +I18nHelper::I18nHelper() {} + +void I18nHelper::setInfo(I18nModule::I18nInfo &&i18nInfo) { + m_i18nInfo = i18nInfo; +} + +std::string I18nHelper::getLocaleIdentifier() const { return m_i18nInfo.first; } -bool I18nModule::getIsRTL() { - return m_i18nInfo.second; +bool I18nHelper::getIsRTL() const { + if (m_forceRTL) { + // Used for debugging purposes, forces RTL even in LTR locales + return true; + } + + // If the app allows RTL (default is true), then we are in RTL if the locale is RTL + return m_allowRTL && m_i18nInfo.second; +} + +void I18nHelper::setAllowRTL(bool allowRTL) { + m_allowRTL = allowRTL; +} + +void I18nHelper::setForceRTL(bool forceRTL) { + m_forceRTL = forceRTL; } } // namespace uwp diff --git a/vnext/ReactUWP/Modules/I18nModule.h b/vnext/ReactUWP/Modules/I18nModule.h index 3e69295bab1..7a171d5209d 100644 --- a/vnext/ReactUWP/Modules/I18nModule.h +++ b/vnext/ReactUWP/Modules/I18nModule.h @@ -10,19 +10,42 @@ namespace react { namespace uwp { +class I18nHelper; + class I18nModule final : public react::windows::II18nModule { public: using I18nInfo = std::pair; static I18nInfo GetI18nInfo(); // Must be called from a UI thread // II18nModule - I18nModule(I18nInfo &&i18nInfo); + I18nModule(); - std::string getLocaleIdentifier() override; - bool getIsRTL() override; + std::string getLocaleIdentifier() const override; + bool getIsRTL() const override; + void setAllowRTL(bool allowRTL) override; + void setForceRTL(bool forceRTL) override; private: - I18nInfo m_i18nInfo; + I18nHelper &m_helper; }; + +class I18nHelper { + public: + static I18nHelper &Instance(); + + I18nHelper(); + + void setInfo(I18nModule::I18nInfo &&i18nInfo); + std::string getLocaleIdentifier() const; + bool getIsRTL() const; + void setAllowRTL(bool allowRTL); + void setForceRTL(bool forceRTL); + + private: + I18nModule::I18nInfo m_i18nInfo; + bool m_allowRTL{true}; + bool m_forceRTL{false}; +}; + } // namespace uwp } // namespace react diff --git a/vnext/ReactUWP/Modules/NativeUIManager.cpp b/vnext/ReactUWP/Modules/NativeUIManager.cpp index 39fde0f6657..14550121ac7 100644 --- a/vnext/ReactUWP/Modules/NativeUIManager.cpp +++ b/vnext/ReactUWP/Modules/NativeUIManager.cpp @@ -3,6 +3,7 @@ #include "pch.h" +#include "I18nModule.h" #include "NativeUIManager.h" #include @@ -185,6 +186,10 @@ void NativeUIManager::AddRootView( XamlView view = xamlRootView->GetXamlView(); m_tagsToXamlReactControl.emplace(shadowNode.m_tag, xamlRootView->GetXamlReactControl()); + // Push the appropriate FlowDirection into the root view. + view.as().FlowDirection( + I18nHelper::Instance().getIsRTL() ? xaml::FlowDirection::RightToLeft : xaml::FlowDirection::LeftToRight); + m_tagsToYogaNodes.emplace(shadowNode.m_tag, make_yoga_node()); auto element = view.as(); @@ -851,7 +856,9 @@ void NativeUIManager::DoLayout() { float actualWidth = static_cast(rootElement.ActualWidth()); float actualHeight = static_cast(rootElement.ActualHeight()); - // TODO: Real direction (VSO 1697992: RTL Layout) + // We must always run layout in LTR mode, which might seem unintuitive. + // We will flip the root of the tree into RTL by forcing the root XAML node's FlowDirection to RightToLeft + // which will inherit down the XAML tree, allowing all native controls to pick it up. YGNodeCalculateLayout(rootNode, actualWidth, actualHeight, YGDirectionLTR); for (auto &tagToYogaNode : m_tagsToYogaNodes) { diff --git a/vnext/ReactWindowsCore/Modules/I18nModule.cpp b/vnext/ReactWindowsCore/Modules/I18nModule.cpp index debb8da029f..824e5ff4707 100644 --- a/vnext/ReactWindowsCore/Modules/I18nModule.cpp +++ b/vnext/ReactWindowsCore/Modules/I18nModule.cpp @@ -2,6 +2,9 @@ // Licensed under the MIT License. #include "I18nModule.h" +#include + +using namespace facebook::xplat; namespace react { namespace windows { @@ -18,7 +21,10 @@ std::map I18nModule::getConstants() { } std::vector I18nModule::getMethods() { - return {}; + return { + Method("allowRTL", [this](folly::dynamic args) { this->m_module->setAllowRTL(jsArgAsBool(args, 0)); }), + Method("forceRTL", [this](folly::dynamic args) { this->m_module->setForceRTL(jsArgAsBool(args, 0)); }), + }; } std::unique_ptr createI18nModule(std::unique_ptr module) { @@ -26,4 +32,4 @@ std::unique_ptr createI18nModule(std::unique } } // namespace windows -} // namespace react \ No newline at end of file +} // namespace react diff --git a/vnext/include/ReactWindowsCore/II18nModule.h b/vnext/include/ReactWindowsCore/II18nModule.h index ce84ef1ade9..b2e0b0efee8 100644 --- a/vnext/include/ReactWindowsCore/II18nModule.h +++ b/vnext/include/ReactWindowsCore/II18nModule.h @@ -10,10 +10,12 @@ namespace windows { struct II18nModule { virtual ~II18nModule(){}; - virtual std::string getLocaleIdentifier() = 0; - virtual bool getIsRTL() = 0; + virtual std::string getLocaleIdentifier() const = 0; + virtual bool getIsRTL() const = 0; + virtual void setAllowRTL(bool allowRTL) = 0; + virtual void setForceRTL(bool forceRTL) = 0; }; std::unique_ptr createI18nModule(std::unique_ptr module); } // namespace windows -} // namespace react \ No newline at end of file +} // namespace react