Skip to content

Commit

Permalink
WIP Right To Left support
Browse files Browse the repository at this point in the history
  • Loading branch information
cwensley committed Oct 21, 2024
1 parent 66d5838 commit c584bd3
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 27 deletions.
5 changes: 5 additions & 0 deletions src/Eto.Mac/AppDelegate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
};
Expand All @@ -129,7 +129,7 @@ public void Invoke(Action action)
else
Control.InvokeOnMainThread(action);
}

public void AsyncInvoke(Action action)
{
Control.BeginInvokeOnMainThread(action);
Expand All @@ -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);
}
Expand Down Expand Up @@ -196,7 +196,7 @@ public void Run()


EtoBundle.Init();

EtoFontManager.Install();

if (Control.Delegate == null)
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/Eto.Mac/Forms/Cells/TextBoxCellHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) { }
Expand Down
6 changes: 5 additions & 1 deletion src/Eto.Mac/Forms/Controls/MacImageTextView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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;

Expand Down
6 changes: 6 additions & 0 deletions src/Eto.Mac/Forms/Controls/SplitterHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions src/Eto.Mac/Forms/MacView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions src/Eto.Mac/Forms/TableLayoutHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
30 changes: 18 additions & 12 deletions src/Eto.Mac/MacConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions src/Eto/Forms/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,19 @@ public string BadgeLabel
/// </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.
Expand Down Expand Up @@ -767,5 +780,6 @@ public void OnIsActiveChanged(Application widget, EventArgs e)
/// 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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

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>
Expand Down Expand Up @@ -885,6 +900,18 @@ internal virtual void InternalEnsureLayout()
/// 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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

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.
Expand Down Expand Up @@ -2030,6 +2057,7 @@ public void OnEnabledChanged(Control widget, EventArgs e)
/// 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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

GitHub Actions / build-mac

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

/// <summary>
/// Captures all mouse events to this control.
Expand Down
8 changes: 8 additions & 0 deletions src/Eto/Forms/Controls/ThemedControlHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 />
Expand Down
1 change: 1 addition & 0 deletions test/Eto.Test.Gtk/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion test/Eto.Test.Mac/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading

0 comments on commit c584bd3

Please sign in to comment.