From 570b7342bd188f48f64f795393b7c53767e7adab Mon Sep 17 00:00:00 2001 From: Muaz Date: Wed, 8 Jul 2026 00:37:16 +0300 Subject: [PATCH 1/9] Fix text box orientation in right to left translation mode With right to left enabled and an original subtitle loaded, the subtitle grid is mirrored (the helper sets FlowDirection on the DataGrid), so the Original column renders on the left. The text edit area below is a plain grid that the helper never touched, so the current and original text boxes kept their left to right positions and no longer lined up with the grid columns above them. Subtitle Edit 4 mirrored both, with the original text box under the original column. The text edit grid now carries a name and the right to left helper mirrors it together with the subtitle grid, so the text boxes, their labels, and the length indicators follow the same orientation. Windows without that grid are unaffected, and the pure right to left UI language path (whole window flow direction) already mirrored everything consistently. --- src/ui/Features/Main/Layout/InitListViewAndEditBox.cs | 3 +++ src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs b/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs index cdc5155d8d8..5d56a273b7e 100644 --- a/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs +++ b/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs @@ -1250,6 +1250,9 @@ public static Grid MakeLayoutListViewAndEditBox(MainView mainPage, MainViewModel // Right panel for text editing (show/duration is to the left) var textEditGrid = new Grid { + // RightToLeftHelper mirrors this grid by name so the current/original + // text boxes keep matching the mirrored subtitle grid columns. + Name = "SubtitleTextEditGrid", ColumnDefinitions = new ColumnDefinitions("*,*,Auto"), RowDefinitions = new RowDefinitions("Auto,*,Auto"), }; diff --git a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs index 3470818f51d..e98f4bc06e8 100644 --- a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs +++ b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs @@ -42,6 +42,13 @@ private static void SetFlowDirectionRecursive(Visual visual, FlowDirection flowD { dataGrid.FlowDirection = flowDirection; } + else if (visual is Grid grid && grid.Name == "SubtitleTextEditGrid") + { + // Mirror the edit area with the subtitle grid: the grid's Original + // column moves to the other side under right to left, and the + // original text box below must follow it (issue #12249). + grid.FlowDirection = flowDirection; + } else if (visual is TextBox textBox) { textBox.FlowDirection = flowDirection; From b51688789361b6e1b570d50d8c8a4f33a4e695b3 Mon Sep 17 00:00:00 2001 From: Muaz Date: Wed, 8 Jul 2026 08:53:41 +0300 Subject: [PATCH 2/9] Mirror the edit grid columns for real instead of via FlowDirection Testing the first version on macOS showed that setting FlowDirection on the edit grid does not rearrange its children; only the DataGrid honors the property for column order, so the subtitle grid mirrored while the text boxes stayed put and the mismatch from the issue remained. The helper now swaps the columns physically: the column definitions are reversed (the original column's width binding travels with its definition object, so the collapse when no original subtitle is loaded keeps working) and every child is remapped to the mirrored column index. The operation is idempotent through the grid's Tag, so repeated passes from the window open and toggle paths cannot double swap. --- .../Main/MainHelpers/RightToLeftHelper.cs | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs index e98f4bc06e8..5ee41f38de4 100644 --- a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs +++ b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs @@ -1,4 +1,5 @@ -using Avalonia; +using System.Linq; +using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.VisualTree; @@ -16,6 +17,40 @@ internal static void SetRightToLeftForDataGridAndText(Window window) SetFlowDirectionRecursive(window, flowDirection); } + /// + /// Mirrors the main text edit grid so the current and original text boxes keep + /// matching the mirrored subtitle grid columns (issue #12249). Setting + /// FlowDirection on a grid does not rearrange its children, so the columns are + /// swapped for real: the column definitions are reversed (the original column's + /// width binding travels with its definition object) and every child is + /// remapped to the mirrored column index. Idempotent via the grid's Tag. + /// + private static void MirrorTextEditGrid(Grid grid, FlowDirection flowDirection) + { + var wantMirrored = flowDirection == FlowDirection.RightToLeft; + var isMirrored = Equals(grid.Tag, "mirrored"); + if (wantMirrored == isMirrored) + { + return; + } + + grid.Tag = wantMirrored ? "mirrored" : null; + + var definitions = grid.ColumnDefinitions.ToList(); + grid.ColumnDefinitions.Clear(); + definitions.Reverse(); + foreach (var definition in definitions) + { + grid.ColumnDefinitions.Add(definition); + } + + var last = definitions.Count - 1; + foreach (var child in grid.Children) + { + Grid.SetColumn(child, last - Grid.GetColumn(child)); + } + } + private static void SetFlowDirectionRecursive(Visual visual, FlowDirection flowDirection) { if (visual is ComboBox) @@ -44,10 +79,7 @@ private static void SetFlowDirectionRecursive(Visual visual, FlowDirection flowD } else if (visual is Grid grid && grid.Name == "SubtitleTextEditGrid") { - // Mirror the edit area with the subtitle grid: the grid's Original - // column moves to the other side under right to left, and the - // original text box below must follow it (issue #12249). - grid.FlowDirection = flowDirection; + MirrorTextEditGrid(grid, flowDirection); } else if (visual is TextBox textBox) { From d5a3150495b6fc64e5e84d690ad182fdddf72486 Mon Sep 17 00:00:00 2001 From: Muaz Date: Wed, 8 Jul 2026 12:55:37 +0300 Subject: [PATCH 3/9] Follow content direction per text box in right to left mode Right to left mode used to force every text box right to left, so with a left to right original subtitle loaded (for example Turkish under an Arabic working language) the original text was pushed to the wrong side of its box and read awkwardly. The list and the boxes now follow the text itself: a control whose content contains right to left letters stays right to left, content in a left to right script aligns left to right, and empty controls keep the right to left direction so typing in a right to left language starts on the correct side. The edit text boxes re-evaluate on every text change, so the direction tracks the selected line while navigating a mixed language subtitle. --- .../Main/Layout/TextEditorBindingHelper.cs | 9 +++++++ .../Main/MainHelpers/RightToLeftHelper.cs | 24 +++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs b/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs index 3bbe46245ee..fcf13f15a3d 100644 --- a/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs +++ b/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs @@ -5,6 +5,7 @@ using Avalonia.Media; using AvaloniaEdit; using Nikse.SubtitleEdit.Features.Shared.TextBoxUtils; +using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Main.Layout; @@ -157,6 +158,14 @@ private void OnTextEditorTextChanged(object? sender, System.EventArgs e) UpdateViewModelFromEditor(); var routedEvent = _isOriginal ? OriginalTextChangedEvent : TextChangedEvent; _vm.SubtitleTextChanged(sender, new TextChangedEventArgs(routedEvent)); + + if (Se.Settings.Appearance.RightToLeft) + { + // Follow the content: an original subtitle in a left to right language + // stays readable next to a right to left working language and vice versa. + _textEditor.TextArea.FlowDirection = MainHelpers.RightToLeftHelper + .GetContentDirection(_textEditor.Text, FlowDirection.RightToLeft); + } } private void OnTextEditorTapped(object? sender, RoutedEventArgs e) diff --git a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs index 5ee41f38de4..0068b794d90 100644 --- a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs +++ b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs @@ -5,6 +5,7 @@ using Avalonia.VisualTree; using AvaloniaEdit.Editing; using Nikse.SubtitleEdit.Controls; +using Nikse.SubtitleEdit.Core.Common; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Main.MainHelpers; @@ -25,6 +26,25 @@ internal static void SetRightToLeftForDataGridAndText(Window window) /// width binding travels with its definition object) and every child is /// remapped to the mirrored column index. Idempotent via the grid's Tag. /// + /// + /// In right to left mode a text control follows its content: text with right + /// to left letters (Arabic, Hebrew, and so on) stays right to left, text in a + /// left to right script (for example a Turkish or English original subtitle) + /// aligns left to right. Empty controls keep the requested direction so typing + /// in a right to left language starts on the correct side. + /// + internal static FlowDirection GetContentDirection(string? text, FlowDirection requested) + { + if (requested != FlowDirection.RightToLeft || string.IsNullOrWhiteSpace(text)) + { + return requested; + } + + return LanguageAutoDetect.ContainsRightToLeftLetter(text) + ? FlowDirection.RightToLeft + : FlowDirection.LeftToRight; + } + private static void MirrorTextEditGrid(Grid grid, FlowDirection flowDirection) { var wantMirrored = flowDirection == FlowDirection.RightToLeft; @@ -83,11 +103,11 @@ private static void SetFlowDirectionRecursive(Visual visual, FlowDirection flowD } else if (visual is TextBox textBox) { - textBox.FlowDirection = flowDirection; + textBox.FlowDirection = GetContentDirection(textBox.Text, flowDirection); } else if (visual is TextArea textArea) { - textArea.FlowDirection = flowDirection; + textArea.FlowDirection = GetContentDirection(textArea.Document?.Text, flowDirection); } foreach (var child in visual.GetVisualChildren()) From 8219128f013740d516dbd100bc59bb904f7413bd Mon Sep 17 00:00:00 2001 From: Muaz Date: Wed, 8 Jul 2026 13:11:52 +0300 Subject: [PATCH 4/9] Apply content direction to the plain text boxes and the grid columns The previous commit only covered the color tags editor, but right to left mode forces that editor off, so the boxes in actual right to left sessions are plain text boxes that received their direction once, while still empty, from the recursive pass at window open. They now follow their content through a property observer attached at creation, and the Text and Original text grid columns get the same treatment through a converter bound per cell: in right to left mode a cell whose content is in a left to right script flows left to right, and in every other case the binding stays unset so the cell keeps the inherited direction. A Turkish original next to an Arabic working language now reads from the left in both the grid and its text box while the Arabic side keeps reading from the right. --- .../Main/Layout/InitListViewAndEditBox.cs | 5 +++ .../Main/MainHelpers/RightToLeftHelper.cs | 18 ++++++++++ .../TextToFlowDirectionConverter.cs | 36 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs diff --git a/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs b/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs index 5d56a273b7e..b03b9395b3a 100644 --- a/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs +++ b/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs @@ -118,6 +118,7 @@ public static Grid MakeLayoutListViewAndEditBox(MainView mainPage, MainViewModel var notNullConverter = new NotNullConverter(); var nullToOpacityConverter = new NullToOpacityConverter(); var syntaxHighlightingConverter = new TextWithSubtitleSyntaxHighlightingConverter(); + var textToFlowDirectionConverter = new TextToFlowDirectionConverter(); vm.SubtitleDataGridSyntaxHighlighting = syntaxHighlightingConverter; // How the Text/Original cells fit their text to the window (feature #11590). Read once here; // the grid is rebuilt when settings are applied, so a changed mode takes effect then. @@ -305,6 +306,7 @@ public static Grid MakeLayoutListViewAndEditBox(MainView mainPage, MainViewModel { VerticalAlignment = VerticalAlignment.Center, [!TextBlock.InlinesProperty] = new Binding(nameof(SubtitleLineViewModel.Text)) { Converter = syntaxHighlightingConverter, Mode = BindingMode.OneWay }, + [!TextBlock.FlowDirectionProperty] = new Binding(nameof(SubtitleLineViewModel.Text)) { Converter = textToFlowDirectionConverter, Mode = BindingMode.OneWay }, }; SubtitleGridTextDisplayModeDisplay.ApplyTo(textBlock, gridTextDisplayMode); @@ -336,6 +338,7 @@ public static Grid MakeLayoutListViewAndEditBox(MainView mainPage, MainViewModel { VerticalAlignment = VerticalAlignment.Center, [!TextBlock.InlinesProperty] = new Binding(nameof(SubtitleLineViewModel.OriginalText)) { Converter = syntaxHighlightingConverter, Mode = BindingMode.OneWay }, + [!TextBlock.FlowDirectionProperty] = new Binding(nameof(SubtitleLineViewModel.OriginalText)) { Converter = textToFlowDirectionConverter, Mode = BindingMode.OneWay }, }; SubtitleGridTextDisplayModeDisplay.ApplyTo(textBlock, gridTextDisplayMode); @@ -1736,6 +1739,7 @@ private static Avalonia.Controls.Control MakeTextBox(MainViewModel vm) textBox.AddHandler(InputElement.PointerPressedEvent, (_, e) => vm.StoreTextEditorPointerArgs(e), RoutingStrategies.Tunnel); SetupMacContextMenuForTextBox(textBox, vm); + MainHelpers.RightToLeftHelper.FollowContentDirection(textBox); vm.EditTextBox = new TextBoxWrapper(textBox); return textBox; @@ -1846,6 +1850,7 @@ private static Avalonia.Controls.Control MakeTextBoxOriginal(MainViewModel vm) } SetupMacContextMenuForTextBox(textBox, vm); + MainHelpers.RightToLeftHelper.FollowContentDirection(textBox); vm.EditTextBoxOriginal = new TextBoxWrapper(textBox); return textBox; diff --git a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs index 0068b794d90..a02c9a83add 100644 --- a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs +++ b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs @@ -45,6 +45,24 @@ internal static FlowDirection GetContentDirection(string? text, FlowDirection re : FlowDirection.LeftToRight; } + /// + /// Keeps a text box's flow direction following its content while right to left + /// mode is enabled: right to left content stays right to left, left to right + /// content (for example a Turkish original next to an Arabic working language) + /// aligns left to right. Attach once when the text box is created; text set + /// while right to left mode is off is left alone. + /// + internal static void FollowContentDirection(TextBox textBox) + { + textBox.PropertyChanged += (_, e) => + { + if (e.Property == TextBox.TextProperty && Se.Settings.Appearance.RightToLeft) + { + textBox.FlowDirection = GetContentDirection(textBox.Text, FlowDirection.RightToLeft); + } + }; + } + private static void MirrorTextEditGrid(Grid grid, FlowDirection flowDirection) { var wantMirrored = flowDirection == FlowDirection.RightToLeft; diff --git a/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs new file mode 100644 index 00000000000..aa4f2350f72 --- /dev/null +++ b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs @@ -0,0 +1,36 @@ +using Avalonia; +using Avalonia.Data.Converters; +using Avalonia.Media; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Logic.Config; +using System; +using System.Globalization; + +namespace Nikse.SubtitleEdit.Logic.ValueConverters; + +/// +/// In right to left mode, gives a text presenter a left to right flow direction +/// when its content is in a left to right script (for example a Turkish original +/// subtitle next to an Arabic working language). In every other case the binding +/// is left unset so the control keeps its inherited direction. +/// +public class TextToFlowDirectionConverter : IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (Se.Settings.Appearance.RightToLeft && + value is string text && + !string.IsNullOrWhiteSpace(text) && + !LanguageAutoDetect.ContainsRightToLeftLetter(text)) + { + return FlowDirection.LeftToRight; + } + + return AvaloniaProperty.UnsetValue; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} From 413978106c2bd61cd5f811164e1ee12f57eb73c9 Mon Sep 17 00:00:00 2001 From: Muaz Date: Wed, 8 Jul 2026 13:15:18 +0300 Subject: [PATCH 5/9] Detect content direction in both modes, not only right to left mode The auto detection used to engage only while right to left mode was on. With a left to right working language and a right to left original (for example a Turkish subtitle with an Arabic original), the Arabic column and text box were forced left to right and read from the wrong side. The detection is now symmetric: content with right to left letters flows right to left in either mode, content in a left to right script flows left to right in either mode, and empty controls follow the active mode so typing starts on the expected side. --- .../Main/Layout/TextEditorBindingHelper.cs | 14 +++++++------- .../Main/MainHelpers/RightToLeftHelper.cs | 9 ++++++--- .../TextToFlowDirectionConverter.cs | 18 +++++++++--------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs b/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs index fcf13f15a3d..62444aa1448 100644 --- a/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs +++ b/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs @@ -159,13 +159,13 @@ private void OnTextEditorTextChanged(object? sender, System.EventArgs e) var routedEvent = _isOriginal ? OriginalTextChangedEvent : TextChangedEvent; _vm.SubtitleTextChanged(sender, new TextChangedEventArgs(routedEvent)); - if (Se.Settings.Appearance.RightToLeft) - { - // Follow the content: an original subtitle in a left to right language - // stays readable next to a right to left working language and vice versa. - _textEditor.TextArea.FlowDirection = MainHelpers.RightToLeftHelper - .GetContentDirection(_textEditor.Text, FlowDirection.RightToLeft); - } + // Follow the content: an original subtitle in a left to right language + // stays readable next to a right to left working language and vice versa. + var requestedDirection = Se.Settings.Appearance.RightToLeft + ? FlowDirection.RightToLeft + : FlowDirection.LeftToRight; + _textEditor.TextArea.FlowDirection = MainHelpers.RightToLeftHelper + .GetContentDirection(_textEditor.Text, requestedDirection); } private void OnTextEditorTapped(object? sender, RoutedEventArgs e) diff --git a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs index a02c9a83add..6c6bb745256 100644 --- a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs +++ b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs @@ -35,7 +35,7 @@ internal static void SetRightToLeftForDataGridAndText(Window window) /// internal static FlowDirection GetContentDirection(string? text, FlowDirection requested) { - if (requested != FlowDirection.RightToLeft || string.IsNullOrWhiteSpace(text)) + if (string.IsNullOrWhiteSpace(text)) { return requested; } @@ -56,9 +56,12 @@ internal static void FollowContentDirection(TextBox textBox) { textBox.PropertyChanged += (_, e) => { - if (e.Property == TextBox.TextProperty && Se.Settings.Appearance.RightToLeft) + if (e.Property == TextBox.TextProperty) { - textBox.FlowDirection = GetContentDirection(textBox.Text, FlowDirection.RightToLeft); + var requested = Se.Settings.Appearance.RightToLeft + ? FlowDirection.RightToLeft + : FlowDirection.LeftToRight; + textBox.FlowDirection = GetContentDirection(textBox.Text, requested); } }; } diff --git a/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs index aa4f2350f72..73303209fe1 100644 --- a/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs +++ b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs @@ -9,21 +9,21 @@ namespace Nikse.SubtitleEdit.Logic.ValueConverters; /// -/// In right to left mode, gives a text presenter a left to right flow direction -/// when its content is in a left to right script (for example a Turkish original -/// subtitle next to an Arabic working language). In every other case the binding -/// is left unset so the control keeps its inherited direction. +/// Gives a text presenter the flow direction of its content: text with right to +/// left letters flows right to left even in left to right mode, and text in a +/// left to right script (for example a Turkish original next to an Arabic +/// working language) flows left to right even in right to left mode. Empty +/// cells leave the binding unset so the control keeps its inherited direction. /// public class TextToFlowDirectionConverter : IValueConverter { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - if (Se.Settings.Appearance.RightToLeft && - value is string text && - !string.IsNullOrWhiteSpace(text) && - !LanguageAutoDetect.ContainsRightToLeftLetter(text)) + if (value is string text && !string.IsNullOrWhiteSpace(text)) { - return FlowDirection.LeftToRight; + return LanguageAutoDetect.ContainsRightToLeftLetter(text) + ? FlowDirection.RightToLeft + : FlowDirection.LeftToRight; } return AvaloniaProperty.UnsetValue; From bbcca60e6a1a697d040e87a767a9ddfb59862642 Mon Sep 17 00:00:00 2001 From: Muaz Date: Wed, 8 Jul 2026 14:59:25 +0300 Subject: [PATCH 6/9] Restore Subtitle Edit 4 behavior in left to right mode This reverts the change that applied content based text direction in both modes. Checking how Subtitle Edit 4 handles this (Main.cs, FixRightToLeftDependingOnLanguage and the right to left mode menu handler): outside right to left mode nothing is direction adjusted at all, in the list or in the text boxes, whatever the content language. Applying content direction in left to right mode changed the appearance of plain single subtitle files compared to both Subtitle Edit 4 and current Subtitle Edit 5, which is out of scope here. Right to left mode keeps the content aware direction from the previous commits (Subtitle Edit 4 does the same per file through FixRightToLeftDependingOnLanguage; per line is a refinement of that), and left to right mode is again untouched. --- .../Main/Layout/TextEditorBindingHelper.cs | 14 +++++++------- .../Main/MainHelpers/RightToLeftHelper.cs | 9 +++------ .../TextToFlowDirectionConverter.cs | 18 +++++++++--------- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs b/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs index 62444aa1448..fcf13f15a3d 100644 --- a/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs +++ b/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs @@ -159,13 +159,13 @@ private void OnTextEditorTextChanged(object? sender, System.EventArgs e) var routedEvent = _isOriginal ? OriginalTextChangedEvent : TextChangedEvent; _vm.SubtitleTextChanged(sender, new TextChangedEventArgs(routedEvent)); - // Follow the content: an original subtitle in a left to right language - // stays readable next to a right to left working language and vice versa. - var requestedDirection = Se.Settings.Appearance.RightToLeft - ? FlowDirection.RightToLeft - : FlowDirection.LeftToRight; - _textEditor.TextArea.FlowDirection = MainHelpers.RightToLeftHelper - .GetContentDirection(_textEditor.Text, requestedDirection); + if (Se.Settings.Appearance.RightToLeft) + { + // Follow the content: an original subtitle in a left to right language + // stays readable next to a right to left working language and vice versa. + _textEditor.TextArea.FlowDirection = MainHelpers.RightToLeftHelper + .GetContentDirection(_textEditor.Text, FlowDirection.RightToLeft); + } } private void OnTextEditorTapped(object? sender, RoutedEventArgs e) diff --git a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs index 6c6bb745256..a02c9a83add 100644 --- a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs +++ b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs @@ -35,7 +35,7 @@ internal static void SetRightToLeftForDataGridAndText(Window window) /// internal static FlowDirection GetContentDirection(string? text, FlowDirection requested) { - if (string.IsNullOrWhiteSpace(text)) + if (requested != FlowDirection.RightToLeft || string.IsNullOrWhiteSpace(text)) { return requested; } @@ -56,12 +56,9 @@ internal static void FollowContentDirection(TextBox textBox) { textBox.PropertyChanged += (_, e) => { - if (e.Property == TextBox.TextProperty) + if (e.Property == TextBox.TextProperty && Se.Settings.Appearance.RightToLeft) { - var requested = Se.Settings.Appearance.RightToLeft - ? FlowDirection.RightToLeft - : FlowDirection.LeftToRight; - textBox.FlowDirection = GetContentDirection(textBox.Text, requested); + textBox.FlowDirection = GetContentDirection(textBox.Text, FlowDirection.RightToLeft); } }; } diff --git a/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs index 73303209fe1..aa4f2350f72 100644 --- a/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs +++ b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs @@ -9,21 +9,21 @@ namespace Nikse.SubtitleEdit.Logic.ValueConverters; /// -/// Gives a text presenter the flow direction of its content: text with right to -/// left letters flows right to left even in left to right mode, and text in a -/// left to right script (for example a Turkish original next to an Arabic -/// working language) flows left to right even in right to left mode. Empty -/// cells leave the binding unset so the control keeps its inherited direction. +/// In right to left mode, gives a text presenter a left to right flow direction +/// when its content is in a left to right script (for example a Turkish original +/// subtitle next to an Arabic working language). In every other case the binding +/// is left unset so the control keeps its inherited direction. /// public class TextToFlowDirectionConverter : IValueConverter { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - if (value is string text && !string.IsNullOrWhiteSpace(text)) + if (Se.Settings.Appearance.RightToLeft && + value is string text && + !string.IsNullOrWhiteSpace(text) && + !LanguageAutoDetect.ContainsRightToLeftLetter(text)) { - return LanguageAutoDetect.ContainsRightToLeftLetter(text) - ? FlowDirection.RightToLeft - : FlowDirection.LeftToRight; + return FlowDirection.LeftToRight; } return AvaloniaProperty.UnsetValue; From 84ef8aaaec15e54ad60bb206cc4e8224999b00e9 Mon Sep 17 00:00:00 2001 From: Muaz Date: Wed, 8 Jul 2026 15:01:14 +0300 Subject: [PATCH 7/9] Move a summary comment to the method it documents --- .../Main/MainHelpers/RightToLeftHelper.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs index a02c9a83add..e009fb50ad0 100644 --- a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs +++ b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs @@ -18,14 +18,6 @@ internal static void SetRightToLeftForDataGridAndText(Window window) SetFlowDirectionRecursive(window, flowDirection); } - /// - /// Mirrors the main text edit grid so the current and original text boxes keep - /// matching the mirrored subtitle grid columns (issue #12249). Setting - /// FlowDirection on a grid does not rearrange its children, so the columns are - /// swapped for real: the column definitions are reversed (the original column's - /// width binding travels with its definition object) and every child is - /// remapped to the mirrored column index. Idempotent via the grid's Tag. - /// /// /// In right to left mode a text control follows its content: text with right /// to left letters (Arabic, Hebrew, and so on) stays right to left, text in a @@ -63,6 +55,14 @@ internal static void FollowContentDirection(TextBox textBox) }; } + /// + /// Mirrors the main text edit grid so the current and original text boxes keep + /// matching the mirrored subtitle grid columns (issue #12249). Setting + /// FlowDirection on a grid does not rearrange its children, so the columns are + /// swapped for real: the column definitions are reversed (the original column's + /// width binding travels with its definition object) and every child is + /// remapped to the mirrored column index. Idempotent via the grid's Tag. + /// private static void MirrorTextEditGrid(Grid grid, FlowDirection flowDirection) { var wantMirrored = flowDirection == FlowDirection.RightToLeft; From 0e3f4821de470dc495ded17c1fea4616fde30fd3 Mon Sep 17 00:00:00 2001 From: Muaz Date: Wed, 8 Jul 2026 15:10:41 +0300 Subject: [PATCH 8/9] Fix right to left cells losing their direction and refresh the grid on toggle The per line flow direction converter returned UnsetValue for right to left content, expecting the cell to inherit the mirrored grid's direction. A binding that yields UnsetValue does not fall back to the inherited value but to the property default (left to right), so in right to left mode every right to left cell rendered left to right. The converter now always produces an explicit direction: content based in right to left mode, left to right otherwise. Since the converter reads the mode setting and bindings do not re-evaluate when a setting changes, toggling the mode also rebinds the subtitle grid's items source (restoring selection and scroll position), so the cells pick up the new mode immediately instead of only after a reload. --- .../Main/MainHelpers/RightToLeftHelper.cs | 23 +++++++++++++++++++ src/ui/Features/Main/MainViewModel.cs | 1 + .../TextToFlowDirectionConverter.cs | 17 ++++++++------ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs index e009fb50ad0..4069d681586 100644 --- a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs +++ b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs @@ -89,6 +89,29 @@ private static void MirrorTextEditGrid(Grid grid, FlowDirection flowDirection) } } + /// + /// Re-runs the subtitle grid's cell bindings after the right to left mode + /// changed: the per line flow direction converter reads the setting, and + /// bindings do not re-evaluate on their own when a setting changes. Rebinding + /// the items source re-creates the rows; selection and scroll position are + /// restored. + /// + internal static void RefreshDataGridBindings(DataGrid? grid, System.Collections.IEnumerable? itemsSource, object? selected) + { + if (grid == null) + { + return; + } + + grid.ItemsSource = null; + grid.ItemsSource = itemsSource; + if (selected != null) + { + grid.SelectedItem = selected; + grid.ScrollIntoView(selected, null); + } + } + private static void SetFlowDirectionRecursive(Visual visual, FlowDirection flowDirection) { if (visual is ComboBox) diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index d2c0a594ece..391b065a4a9 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -11065,6 +11065,7 @@ private void RightToLeftToggle() Se.Settings.Appearance.RightToLeft = !Se.Settings.Appearance.RightToLeft; IsRightToLeftEnabled = Se.Settings.Appearance.RightToLeft; RightToLeftHelper.SetRightToLeftForDataGridAndText(Window); + RightToLeftHelper.RefreshDataGridBindings(SubtitleGrid, Subtitles, SelectedSubtitle); return; } diff --git a/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs index aa4f2350f72..776e30c1771 100644 --- a/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs +++ b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs @@ -1,4 +1,3 @@ -using Avalonia; using Avalonia.Data.Converters; using Avalonia.Media; using Nikse.SubtitleEdit.Core.Common; @@ -18,15 +17,19 @@ public class TextToFlowDirectionConverter : IValueConverter { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - if (Se.Settings.Appearance.RightToLeft && - value is string text && - !string.IsNullOrWhiteSpace(text) && - !LanguageAutoDetect.ContainsRightToLeftLetter(text)) + var rightToLeftMode = Se.Settings.Appearance.RightToLeft; + if (rightToLeftMode && value is string text && !string.IsNullOrWhiteSpace(text)) { - return FlowDirection.LeftToRight; + return LanguageAutoDetect.ContainsRightToLeftLetter(text) + ? FlowDirection.RightToLeft + : FlowDirection.LeftToRight; } - return AvaloniaProperty.UnsetValue; + // Always produce an explicit direction: a binding that yields UnsetValue + // does not fall back to the inherited direction but to the property + // default (left to right), which left right to left cells misaligned in + // right to left mode. + return rightToLeftMode ? FlowDirection.RightToLeft : FlowDirection.LeftToRight; } public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) From db66a4fe0289691a1523aca5020af438297cdc52 Mon Sep 17 00:00:00 2001 From: Muaz Date: Wed, 8 Jul 2026 15:22:56 +0300 Subject: [PATCH 9/9] Align right to left content in left to right mode as well Testing all four arrangements showed one remaining gap: with a left to right working language and a right to left original (for example Turkish plus an Arabic original) in left to right mode, the original column and text box rendered the right to left text left aligned. Text direction now follows the content in either mode, in the grid cells and in the text boxes, while the layout itself (column order, box positions) still follows only the mode toggle exactly like Subtitle Edit 4. Empty controls follow the active mode so typing starts on the expected side. The earlier attempt at this was reverted because the converter still returned UnsetValue for right to left content; with the converter producing explicit directions and the grid rebinding on toggle, the content based behavior is correct in all four arrangements: each language reads from its own side whatever the mode, and the mode only decides the layout. --- .../Main/Layout/TextEditorBindingHelper.cs | 14 ++++++------- .../Main/MainHelpers/RightToLeftHelper.cs | 20 +++++++++++-------- .../TextToFlowDirectionConverter.cs | 11 +++++----- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs b/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs index fcf13f15a3d..62444aa1448 100644 --- a/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs +++ b/src/ui/Features/Main/Layout/TextEditorBindingHelper.cs @@ -159,13 +159,13 @@ private void OnTextEditorTextChanged(object? sender, System.EventArgs e) var routedEvent = _isOriginal ? OriginalTextChangedEvent : TextChangedEvent; _vm.SubtitleTextChanged(sender, new TextChangedEventArgs(routedEvent)); - if (Se.Settings.Appearance.RightToLeft) - { - // Follow the content: an original subtitle in a left to right language - // stays readable next to a right to left working language and vice versa. - _textEditor.TextArea.FlowDirection = MainHelpers.RightToLeftHelper - .GetContentDirection(_textEditor.Text, FlowDirection.RightToLeft); - } + // Follow the content: an original subtitle in a left to right language + // stays readable next to a right to left working language and vice versa. + var requestedDirection = Se.Settings.Appearance.RightToLeft + ? FlowDirection.RightToLeft + : FlowDirection.LeftToRight; + _textEditor.TextArea.FlowDirection = MainHelpers.RightToLeftHelper + .GetContentDirection(_textEditor.Text, requestedDirection); } private void OnTextEditorTapped(object? sender, RoutedEventArgs e) diff --git a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs index 4069d681586..8fcc20e80e1 100644 --- a/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs +++ b/src/ui/Features/Main/MainHelpers/RightToLeftHelper.cs @@ -19,15 +19,16 @@ internal static void SetRightToLeftForDataGridAndText(Window window) } /// - /// In right to left mode a text control follows its content: text with right - /// to left letters (Arabic, Hebrew, and so on) stays right to left, text in a - /// left to right script (for example a Turkish or English original subtitle) - /// aligns left to right. Empty controls keep the requested direction so typing - /// in a right to left language starts on the correct side. + /// A text control follows its content in either mode: text with right to left + /// letters (Arabic, Hebrew, and so on) flows right to left, text in a left to + /// right script (for example a Turkish or English original subtitle) flows + /// left to right. The layout itself still follows only the mode toggle; this + /// governs text direction inside a control. Empty controls keep the requested + /// direction so typing starts on the side matching the active mode. /// internal static FlowDirection GetContentDirection(string? text, FlowDirection requested) { - if (requested != FlowDirection.RightToLeft || string.IsNullOrWhiteSpace(text)) + if (string.IsNullOrWhiteSpace(text)) { return requested; } @@ -48,9 +49,12 @@ internal static void FollowContentDirection(TextBox textBox) { textBox.PropertyChanged += (_, e) => { - if (e.Property == TextBox.TextProperty && Se.Settings.Appearance.RightToLeft) + if (e.Property == TextBox.TextProperty) { - textBox.FlowDirection = GetContentDirection(textBox.Text, FlowDirection.RightToLeft); + var requested = Se.Settings.Appearance.RightToLeft + ? FlowDirection.RightToLeft + : FlowDirection.LeftToRight; + textBox.FlowDirection = GetContentDirection(textBox.Text, requested); } }; } diff --git a/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs index 776e30c1771..854230a5593 100644 --- a/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs +++ b/src/ui/Logic/ValueConverters/TextToFlowDirectionConverter.cs @@ -8,17 +8,18 @@ namespace Nikse.SubtitleEdit.Logic.ValueConverters; /// -/// In right to left mode, gives a text presenter a left to right flow direction -/// when its content is in a left to right script (for example a Turkish original -/// subtitle next to an Arabic working language). In every other case the binding -/// is left unset so the control keeps its inherited direction. +/// Gives a text presenter the flow direction of its content in either mode: cells +/// with right to left letters flow right to left (also as the original next to a +/// left to right working language), cells in a left to right script flow left to +/// right (also as the original next to a right to left working language). Empty +/// cells follow the active mode. /// public class TextToFlowDirectionConverter : IValueConverter { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { var rightToLeftMode = Se.Settings.Appearance.RightToLeft; - if (rightToLeftMode && value is string text && !string.IsNullOrWhiteSpace(text)) + if (value is string text && !string.IsNullOrWhiteSpace(text)) { return LanguageAutoDetect.ContainsRightToLeftLetter(text) ? FlowDirection.RightToLeft