Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Right To Left support #2734

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions src/Eto.Mac/AppDelegate.cs
Original file line number Diff line number Diff line change
@@ -48,6 +48,11 @@ public override NSApplicationTerminateReply ApplicationShouldTerminate(NSApplica
}
return args.Cancel ? NSApplicationTerminateReply.Cancel : NSApplicationTerminateReply.Now;
}

public override void WillTerminate(NSNotification notification)
{
ApplicationHandler.ResetRtlPreference();
}
}
}

59 changes: 49 additions & 10 deletions src/Eto.Mac/Forms/ApplicationHandler.cs
Original file line number Diff line number Diff line change
@@ -115,7 +115,7 @@ static void restart_WillTerminate(object sender, EventArgs e)
var args = new string[]
{
"-c",
"open \"$1\"",
"open \"$1\"",
string.Empty,
NSBundle.MainBundle.BundlePath
};
@@ -129,7 +129,7 @@ public void Invoke(Action action)
else
Control.InvokeOnMainThread(action);
}

public void AsyncInvoke(Action action)
{
Control.BeginInvokeOnMainThread(action);
@@ -150,23 +150,23 @@ public void Restart()
Control.Delegate = oldDelegate;
}

static readonly IntPtr selNextEventMatchingMaskUntilDateInModeDequeue_Handle = Selector.GetHandle ("nextEventMatchingMask:untilDate:inMode:dequeue:");
static readonly IntPtr selSendEvent_Handle = Selector.GetHandle ("sendEvent:");
static readonly IntPtr selNextEventMatchingMaskUntilDateInModeDequeue_Handle = Selector.GetHandle("nextEventMatchingMask:untilDate:inMode:dequeue:");
static readonly IntPtr selSendEvent_Handle = Selector.GetHandle("sendEvent:");

public void RunIteration()
{
MacView.InMouseTrackingLoop = false;
// drain the event queue only for a short period of time so it doesn't lock up
var date = NSDate.FromTimeIntervalSinceNow(0.001);
for (;;)
for (; ; )
{
// dequeue the event
var evt = Control.NextEvent(NSEventMask.AnyEvent, date, NSRunLoopMode.Default, true);

// no event? cool, let's get out of here
if (evt == null)
break;

// dispatch the event
Control.SendEvent(evt);
}
@@ -196,7 +196,7 @@ public void Run()


EtoBundle.Init();

EtoFontManager.Install();

if (Control.Delegate == null)
@@ -227,7 +227,7 @@ public void Open(string url)

#if Mac64
delegate void UncaughtExceptionHandlerDelegate(IntPtr nsexceptionPtr);

[DllImport(Constants.FoundationLibrary)]
static extern void NSSetUncaughtExceptionHandler(UncaughtExceptionHandlerDelegate handler);

@@ -325,5 +325,44 @@ public void EnableFullScreen()
public Keys AlternateModifier => Keys.Alt;

public bool IsActive => NSApplication.SharedApplication.Active;

public LayoutDirection DefaultLayoutDirection
{
get => NSApplication.SharedApplication.UserInterfaceLayoutDirection switch
{
NSApplicationLayoutDirection.LeftToRight => LayoutDirection.LeftToRight,
NSApplicationLayoutDirection.RightToLeft => LayoutDirection.RightToLeft,
_ => LayoutDirection.LeftToRight
};
set
{
var rtl = value == LayoutDirection.RightToLeft;
// NSUserDefaults.StandardUserDefaults.RegisterDefaults(NSDictionary.FromObjectsAndKeys(
// new NSObject[] { NSNumber.FromBoolean(rtl), NSNumber.FromBoolean(rtl) },
// new NSObject[] { new NSString("NSForceRightToLeftWritingDirection"), new NSString("AppleTextDirection") }
// ));
// Environment.SetEnvironmentVariable("NSForceRightToLeftWritingDirection", "YES");
// Environment.SetEnvironmentVariable("AppleTextDirection", "YES");
// Control.WillTerminate -= ResetRtlPreference;
if (rtl)
{
NSUserDefaults.StandardUserDefaults.SetValueForKey(NSNumber.FromBoolean(rtl), new NSString("NSForceRightToLeftWritingDirection"));
NSUserDefaults.StandardUserDefaults.SetValueForKey(NSNumber.FromBoolean(rtl), new NSString("AppleTextDirection"));
}
else
{
NSUserDefaults.StandardUserDefaults.RemoveObject(new NSString("NSForceRightToLeftWritingDirection"));
NSUserDefaults.StandardUserDefaults.RemoveObject(new NSString("AppleTextDirection"));
}
NSUserDefaults.StandardUserDefaults.Synchronize();
}
}

internal static void ResetRtlPreference()
{
// don't actually save these
NSUserDefaults.StandardUserDefaults.RemoveObject(new NSString("NSForceRightToLeftWritingDirection"));
NSUserDefaults.StandardUserDefaults.RemoveObject(new NSString("AppleTextDirection"));
}
}
}
2 changes: 1 addition & 1 deletion src/Eto.Mac/Forms/Cells/ImageTextCellHandler.cs
Original file line number Diff line number Diff line change
@@ -227,7 +227,7 @@ public override NSView GetViewForItem(NSTableView tableView, NSTableColumn table

var cell = view.TextCell;
cell.VerticalAlignment = VerticalAlignment;
cell.Alignment = TextAlignment.ToNS();
cell.Alignment = TextAlignment.ToNS(view.UserInterfaceLayoutDirection);

view.Tag = row;
view.Item = obj;
3 changes: 2 additions & 1 deletion src/Eto.Mac/Forms/Cells/TextBoxCellHandler.cs
Original file line number Diff line number Diff line change
@@ -100,12 +100,13 @@ public CellView()
{
Wraps = false,
Scrollable = true,
UsesSingleLineMode = false // true prevents proper vertical alignment
UsesSingleLineMode = false, // true prevents proper vertical alignment
};
Selectable = false;
DrawsBackground = false;
Bezeled = false;
Bordered = false;
Alignment = NSTextAlignment.Right;
AutoresizingMask = NSViewResizingMask.HeightSizable | NSViewResizingMask.WidthSizable;
}
public CellView(IntPtr handle) : base(handle) { }
6 changes: 5 additions & 1 deletion src/Eto.Mac/Forms/Controls/MacImageTextView.cs
Original file line number Diff line number Diff line change
@@ -33,7 +33,8 @@ void SetSizes(CGSize bounds)
var scaledHeight = Math.Min(imageSize.Height, bounds.Height);
var scaledWidth = imageSize.Width * scaledHeight / imageSize.Height;
_imageSize = new CGSize(scaledWidth, scaledHeight);
TextField.Frame = new CGRect(scaledWidth + ImagePadding, 0, bounds.Width - scaledWidth - ImagePadding, bounds.Height);
var isrtl = UserInterfaceLayoutDirection == NSUserInterfaceLayoutDirection.RightToLeft;
TextField.Frame = new CGRect(isrtl ? 0 : scaledWidth + ImagePadding, 0, bounds.Width - scaledWidth - ImagePadding, bounds.Height);
}
}

@@ -91,6 +92,9 @@ public override void DrawRect(CGRect dirtyRect)

var imageRect = new CGRect(0, bounds.Y, _imageSize.Width, _imageSize.Height);
imageRect.Y += (bounds.Height - _imageSize.Height) / 2;
var isrtl = UserInterfaceLayoutDirection == NSUserInterfaceLayoutDirection.RightToLeft;
if (isrtl)
imageRect.X += bounds.Width - _imageSize.Width;

const float alpha = 1; //Enabled ? 1 : (nfloat)0.5;

6 changes: 6 additions & 0 deletions src/Eto.Mac/Forms/Controls/SplitterHandler.cs
Original file line number Diff line number Diff line change
@@ -229,6 +229,12 @@ void ResizeSubviews(CGSize oldSize2)
panel1Rect.Width = panel1Rect.X = panel2Rect.Width = panel2Rect.X = 0;
if (newFrame.Height <= 0)
panel1Rect.Height = panel1Rect.Y = panel2Rect.Height = panel2Rect.Y = 0;

if (splitView.IsVertical && splitView.UserInterfaceLayoutDirection == NSUserInterfaceLayoutDirection.RightToLeft)
{
panel1Rect.X = panel2Rect.Width + dividerThickness;
panel2Rect.X = 0;
}

splitView.Subviews[0].Frame = panel1Rect;
splitView.Subviews[1].Frame = panel2Rect;
21 changes: 21 additions & 0 deletions src/Eto.Mac/Forms/MacView.cs
Original file line number Diff line number Diff line change
@@ -750,6 +750,11 @@ public virtual void InvalidateMeasure()
Widget.VisualParent.GetMacControl()?.InvalidateMeasure();
}

protected override void Initialize()
{
base.Initialize();
}

protected virtual SizeF GetNaturalSize(SizeF availableSize)
{
var naturalSize = NaturalSize;
@@ -1729,6 +1734,22 @@ public virtual void OnViewDidMoveToWindow()

public bool IsMouseCaptured => MacView.CapturedControl == this;

public LayoutDirection LayoutDirection
{
get => EventControl.UserInterfaceLayoutDirection switch
{
NSUserInterfaceLayoutDirection.LeftToRight => LayoutDirection.LeftToRight,
NSUserInterfaceLayoutDirection.RightToLeft => LayoutDirection.RightToLeft,
_ => LayoutDirection.LeftToRight
};
set => EventControl.UserInterfaceLayoutDirection = value switch
{
LayoutDirection.LeftToRight => NSUserInterfaceLayoutDirection.LeftToRight,
LayoutDirection.RightToLeft => NSUserInterfaceLayoutDirection.RightToLeft,
_ => NSUserInterfaceLayoutDirection.LeftToRight,
};
}

public bool CaptureMouse()
{
if (!Widget.Loaded || !Widget.Visible)
4 changes: 4 additions & 0 deletions src/Eto.Mac/Forms/TableLayoutHandler.cs
Original file line number Diff line number Diff line change
@@ -276,6 +276,7 @@ void PerformLayout()
#endif

float starty = Padding.Top;
var isrtl = Control.UserInterfaceLayoutDirection == NSUserInterfaceLayoutDirection.RightToLeft;
for (int y = 0; y < final_heights.Length; y++)
{
float startx = Padding.Left;
@@ -290,6 +291,9 @@ void PerformLayout()
frame.Width = final_widths[x];
frame.Height = final_heights[y];
frame.X = (nfloat)Math.Round(Math.Max(0, startx));
if (isrtl)
frame.X = controlSize.Width - frame.X - frame.Width;

frame.Y = (nfloat)Math.Round(flipped ? starty : controlSize.Height - starty - frame.Height);
if (frame != oldframe)
macView.SetAlignmentFrame(frame);
30 changes: 18 additions & 12 deletions src/Eto.Mac/MacConversions.cs
Original file line number Diff line number Diff line change
@@ -463,19 +463,25 @@ public static TextAlignment ToEto(this NSTextAlignment align)
}
}

public static NSTextAlignment ToNS(this TextAlignment align)
public static NSTextAlignment ToNS(this TextAlignment align, NSUserInterfaceLayoutDirection? direction = null)
{
switch (align)
{
case TextAlignment.Left:
return NSTextAlignment.Left;
case TextAlignment.Center:
return NSTextAlignment.Center;
case TextAlignment.Right:
return NSTextAlignment.Right;
default:
throw new NotSupportedException();
}
var dir = direction ?? (NSUserInterfaceLayoutDirection)NSApplication.SharedApplication.UserInterfaceLayoutDirection;
if (dir == NSUserInterfaceLayoutDirection.RightToLeft)
return align switch
{
TextAlignment.Left => NSTextAlignment.Right,
TextAlignment.Right => NSTextAlignment.Left,
TextAlignment.Center => NSTextAlignment.Center,
_ => throw new NotSupportedException()
};
else
return align switch
{
TextAlignment.Left => NSTextAlignment.Left,
TextAlignment.Right => NSTextAlignment.Right,
TextAlignment.Center => NSTextAlignment.Center,
_ => throw new NotSupportedException()
};
}

public static Font ToEto(this NSFont font)
14 changes: 14 additions & 0 deletions src/Eto/Forms/Application.cs
Original file line number Diff line number Diff line change
@@ -539,6 +539,19 @@
/// </summary>
/// <seealso cref="IsActiveChanged"/>
public bool IsActive => Handler.IsActive;


/// <summary>
/// Gets or sets the default layout direction for the user interface of the application.
/// </summary>
/// <remarks>
///
/// </remarks>
public LayoutDirection DefaultLayoutDirection
{
get => Handler.DefaultLayoutDirection;
set => Handler.DefaultLayoutDirection = value;
}

/// <summary>
/// Advanced. Runs an iteration of the main UI loop when you are blocking the UI thread with logic.
@@ -767,5 +780,6 @@
/// Gets a value indicating that the application is currently the active application
/// </summary>
bool IsActive { get; }
LayoutDirection DefaultLayoutDirection { get; set; }

Check failure on line 783 in src/Eto/Forms/Application.cs

GitHub Actions / build-windows

Missing XML comment for publicly visible type or member 'Application.IHandler.DefaultLayoutDirection'

Check failure on line 783 in src/Eto/Forms/Application.cs

GitHub Actions / build-windows

Missing XML comment for publicly visible type or member 'Application.IHandler.DefaultLayoutDirection'

Check failure on line 783 in src/Eto/Forms/Application.cs

GitHub Actions / build-mac

Missing XML comment for publicly visible type or member 'Application.IHandler.DefaultLayoutDirection'

Check failure on line 783 in src/Eto/Forms/Application.cs

GitHub Actions / build-mac

Missing XML comment for publicly visible type or member 'Application.IHandler.DefaultLayoutDirection'

Check failure on line 783 in src/Eto/Forms/Application.cs

GitHub Actions / build-mac

Missing XML comment for publicly visible type or member 'Application.IHandler.DefaultLayoutDirection'
}
}
28 changes: 28 additions & 0 deletions src/Eto/Forms/Controls/Control.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
namespace Eto.Forms;

/// <summary>
/// Specifies the layout direction of the user interface.
/// </summary>
public enum LayoutDirection
{
/// <summary>
/// Indicates the user interface should be laid out from left to right
/// </summary>
LeftToRight,
/// <summary>
/// Indicates the user interface should be laid out from right to left
/// </summary>
RightToLeft
}

/// <summary>
/// Base for all visual UI elements
/// </summary>
@@ -885,6 +900,18 @@
/// Releases the mouse capture after a call to <see cref="CaptureMouse"/>.
/// </summary>
public void ReleaseMouseCapture() => Handler.ReleaseMouseCapture();

/// <summary>
/// Gets or sets the layout direction for this control explicitly.
/// </summary>
/// <remarks>
/// The default layout direction of a control is based on the <see cref="Application.LayoutDirection"/>.

Check failure on line 908 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-windows

XML comment has cref attribute 'LayoutDirection' that could not be resolved

Check failure on line 908 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-windows

XML comment has cref attribute 'LayoutDirection' that could not be resolved

Check failure on line 908 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-mac

XML comment has cref attribute 'LayoutDirection' that could not be resolved

Check failure on line 908 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-mac

XML comment has cref attribute 'LayoutDirection' that could not be resolved

Check failure on line 908 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-mac

XML comment has cref attribute 'LayoutDirection' that could not be resolved
/// </remarks>
public LayoutDirection LayoutDirection
{
get => Handler.LayoutDirection;
set => Handler.LayoutDirection = value;
}

/// <summary>
/// Gets or sets the width of the control size.
@@ -2030,6 +2057,7 @@
/// or it can be captured explicitly via <see cref="CaptureMouse"/>.
/// </remarks>
bool IsMouseCaptured { get; }
LayoutDirection LayoutDirection { get; set; }

Check failure on line 2060 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-windows

Missing XML comment for publicly visible type or member 'Control.IHandler.LayoutDirection'

Check failure on line 2060 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-windows

Missing XML comment for publicly visible type or member 'Control.IHandler.LayoutDirection'

Check failure on line 2060 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-mac

Missing XML comment for publicly visible type or member 'Control.IHandler.LayoutDirection'

Check failure on line 2060 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-mac

Missing XML comment for publicly visible type or member 'Control.IHandler.LayoutDirection'

Check failure on line 2060 in src/Eto/Forms/Controls/Control.cs

GitHub Actions / build-mac

Missing XML comment for publicly visible type or member 'Control.IHandler.LayoutDirection'

/// <summary>
/// Captures all mouse events to this control.
8 changes: 8 additions & 0 deletions src/Eto/Forms/Controls/ThemedControlHandler.cs
Original file line number Diff line number Diff line change
@@ -468,6 +468,14 @@ public override void AttachEvent(string id)

/// <inheritdoc />
public bool IsMouseCaptured => Control.IsMouseCaptured;

/// <inheritdoc />
public LayoutDirection LayoutDirection
{
get => Control.LayoutDirection;
set => Control.LayoutDirection = value;
}

/// <inheritdoc />
public bool CaptureMouse() => Control.CaptureMouse();
/// <inheritdoc />
1 change: 1 addition & 0 deletions test/Eto.Test.Gtk/Startup.cs
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ static void Main(string[] args)
platform.Add<INativeHostControls>(() => new NativeHostControls());

var app = new TestApplication(platform);
global::Gtk.Widget.DefaultDirection = global::Gtk.TextDirection.Rtl;
app.TestAssemblies.Add(typeof(Startup).Assembly);
app.Run();
}
2 changes: 1 addition & 1 deletion test/Eto.Test.Mac/Info.plist
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
<key>CFBundleIconFile</key>
<string>TestIcon.icns</string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.Eto.Test.Mac</string>
<string>ca.picoe.Eto.Test.Mac</string>
<key>CFBundleName</key>
<string>Eto.Test.Mac</string>
<key>CFBundleVersion</key>
Loading
Loading