Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/Controls/src/Core/Handlers/Shell/ShellHandler.Windows.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#nullable disable
using System;
using System.Runtime.InteropServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Controls.Platform;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
Expand All @@ -12,6 +14,14 @@ namespace Microsoft.Maui.Controls.Handlers
{
public partial class ShellHandler : ViewHandler<Shell, ShellView>
{
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("user32.dll", SetLastError = true)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

const int GWL_EXSTYLE = -20;
const int WS_EX_LAYOUTRTL = 0x00400000;
Comment thread
Shalini-Ashokan marked this conversation as resolved.
ScrollViewer _scrollViewer;
double? _topAreaHeight = null;
double? _headerHeight = null;
Expand Down Expand Up @@ -164,6 +174,23 @@ public static void MapFlyout(ShellHandler handler, IFlyoutView flyoutView)
internal static void MapFlowDirection(ShellHandler handler, Shell view)
{
handler.PlatformView.UpdateFlowDirection(view);
var windowRootView = handler.MauiContext?.GetNavigationRootManager()?.RootView as WindowRootView;
var window = handler.MauiContext?.Services?.GetService<Microsoft.UI.Xaml.Window>();

if (window == null)
return;

var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
var isRtl = view.FlowDirection == FlowDirection.RightToLeft;
var exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[major] Logic and Correctness — This derives the native window RTL state from Shell.FlowDirection == RightToLeft, which treats MatchParent as LTR and can clear WS_EX_LAYOUTRTL even when the Shell is effectively RTL through inheritance/window flow direction. Concrete scenario: an app sets the Window/root flow direction to RTL while Shell remains at its default MatchParent; this mapper will remove the window RTL style and put title-bar buttons back on the right, reintroducing overlap. Use the effective flow direction or route through the existing Window flow-direction mapping so Shell and Window state do not fight.


// Apply or remove WS_EX_LAYOUTRTL to mirror the window including title bar buttons
var newExStyle = isRtl ? (exStyle | WS_EX_LAYOUTRTL) : (exStyle & ~WS_EX_LAYOUTRTL);
if (exStyle != newExStyle)
SetWindowLong(hwnd, GWL_EXSTYLE, newExStyle);
Comment thread
Shalini-Ashokan marked this conversation as resolved.

if (windowRootView != null)
windowRootView.FlowDirection = isRtl ? Microsoft.UI.Xaml.FlowDirection.RightToLeft : Microsoft.UI.Xaml.FlowDirection.LeftToRight;
}

public static void MapIsPresented(ShellHandler handler, IFlyoutView flyoutView)
Expand Down
59 changes: 59 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue32476.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 32476, "Binding RTL FlowDirection in Shell causes Flyout MenuIcon and native window controls to overlap", PlatformAffected.UWP)]
public class Issue32476 : Shell
{
public Issue32476()
{
FlyoutBehavior = FlyoutBehavior.Flyout;
var button = new Button
{
Text = "Toggle FlowDirection to RTL",
AutomationId = "ToggleButton"
};

var buttonTapLabel = new Label
{
Text = "Tap the button to toggle FlowDirection to RTL",
HorizontalOptions = LayoutOptions.Center
};

button.Clicked += (s, e) =>
{
buttonTapLabel.Text = "Toggling FlowDirection...";
FlowDirection = FlowDirection.RightToLeft;
buttonTapLabel.Text = "FlowDirection is now RTL";
};

var contentPage = new ContentPage
{
Content = new VerticalStackLayout
{
Padding = 20,
Spacing = 10,
Children =
{
new Label
{
Text = "Tap the button to toggle FlowDirection to RTL. The Flyout MenuIcon must not overlap with native window controls.",
LineBreakMode = LineBreakMode.WordWrap
},
button,
buttonTapLabel
}
}
};
Items.Add(new FlyoutItem
{
Title = "Home",
Items =
{
new ShellContent
{
Title = "Main",
Content = contentPage
}
}
});
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#if MACCATALYST || WINDOWS // Native window controls (minimize, maximize, close) are applicable only on Windows and MacCatalyst
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;
namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue32476 : _IssuesUITest
{
public Issue32476(TestDevice testDevice) : base(testDevice)
{
}
public override string Issue => "Binding RTL FlowDirection in Shell causes Flyout MenuIcon and native window controls to overlap";
[Test]
[Category(UITestCategories.Shell)]
public void ShellRTLFlowDirectionShouldNotCauseOverlap()
{
App.WaitForElement("ToggleButton");
App.Tap("ToggleButton");
App.Tap("ToggleButton");

// wait for window layout to update after flow direction change.
Task.Delay(500).Wait();
VerifyScreenshot(includeTitleBar: true);
}
}
#endif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading