From a88b0c4e32c917051b29e9757e1379eccef436b2 Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Tue, 10 Dec 2024 13:46:20 -0800 Subject: [PATCH] TextArea.ScrollToStart/End fixes --- lib/monomac | 2 +- src/Eto.Gtk/Forms/Controls/TextAreaHandler.cs | 30 +++++++---- src/Eto.Mac/Forms/Controls/TextAreaHandler.cs | 37 +++++++++++-- .../Forms/Controls/TextAreaHandler.cs | 16 ++++-- src/Eto.Wpf/Forms/Controls/TextAreaHandler.cs | 28 +++++++++- src/Eto/Forms/Controls/TextArea.cs | 4 +- test/Eto.Test/Handlers/TabControlHandler.cs | 2 +- test/Eto.Test/LoremGenerator.cs | 53 +++++++++++++++++++ .../Sections/Controls/LabelSection.cs | 6 +-- .../Sections/Controls/RichTextAreaSection.cs | 2 +- .../Sections/Controls/TextAreaSection.cs | 2 +- .../Sections/Drawing/FormattedTextSection.cs | 4 +- .../UnitTests/Forms/Controls/LabelTests.cs | 2 +- .../UnitTests/Forms/Controls/TextAreaTests.cs | 22 ++++++++ .../UnitTests/Forms/MessageBoxTests.cs | 16 +++--- test/Eto.Test/UnitTests/Forms/WindowTests.cs | 7 +-- test/Eto.Test/Utility.cs | 30 ----------- 17 files changed, 191 insertions(+), 72 deletions(-) create mode 100644 test/Eto.Test/LoremGenerator.cs delete mode 100755 test/Eto.Test/Utility.cs diff --git a/lib/monomac b/lib/monomac index 86f4bad5f2..60a3752acf 160000 --- a/lib/monomac +++ b/lib/monomac @@ -1 +1 @@ -Subproject commit 86f4bad5f285cb5a601e7aa6b3b897d2e96f9b64 +Subproject commit 60a3752acfe2b651b55e3456953272f48f8632c5 diff --git a/src/Eto.Gtk/Forms/Controls/TextAreaHandler.cs b/src/Eto.Gtk/Forms/Controls/TextAreaHandler.cs index 28f36604c3..ecf31bbc01 100644 --- a/src/Eto.Gtk/Forms/Controls/TextAreaHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/TextAreaHandler.cs @@ -371,20 +371,32 @@ public virtual void ScrollTo(Range range) Control.ScrollToMark(mark, 0, false, 0, 0); } + double GetScrollX() => Control.Direction switch + { + Gtk.TextDirection.Rtl => Control.Justification switch + { + Gtk.Justification.Right => scroll.Hadjustment.Lower, + Gtk.Justification.Center => (scroll.Hadjustment.Upper - scroll.Hadjustment.Lower - scroll.Hadjustment.PageSize) / 2, + _ => scroll.Hadjustment.Upper, + }, + _ => Control.Justification switch + { + Gtk.Justification.Right => scroll.Hadjustment.Upper, + Gtk.Justification.Center => (scroll.Hadjustment.Upper - scroll.Hadjustment.Lower - scroll.Hadjustment.PageSize) / 2, + _ => scroll.Hadjustment.Lower, + }, + }; + public virtual void ScrollToEnd() { - var end = Control.Buffer.EndIter; - var mark = Control.Buffer.CreateMark(null, end, false); - Control.ScrollToMark(mark, 0, false, 0, 0); + scroll.Vadjustment.Value = scroll.Vadjustment.Upper - scroll.Vadjustment.PageSize; + scroll.Hadjustment.Value = GetScrollX(); } public virtual void ScrollToStart() { - var end = Control.Buffer.StartIter; - var mark = Control.Buffer.CreateMark(null, end, false); - Control.ScrollToMark(mark, 0, false, 0, 0); - } - - + scroll.Vadjustment.Value = scroll.Vadjustment.Lower; + scroll.Hadjustment.Value = GetScrollX(); + } } } diff --git a/src/Eto.Mac/Forms/Controls/TextAreaHandler.cs b/src/Eto.Mac/Forms/Controls/TextAreaHandler.cs index f678a293a2..bd18416c1a 100644 --- a/src/Eto.Mac/Forms/Controls/TextAreaHandler.cs +++ b/src/Eto.Mac/Forms/Controls/TextAreaHandler.cs @@ -452,11 +452,40 @@ public BorderType Border } public int TextLength => (int)Control.TextStorage.Length; - - public void ScrollTo(Range range) => Control.ScrollRangeToVisible(range.ToNS()); - public void ScrollToStart() => ScrollTo(new Range(0)); + public void ScrollTo(Range range) + { + var nsRange = range.ToNS(); + Control.LayoutManager.EnsureLayoutForCharacterRange(nsRange); + Control.ScrollRangeToVisible(nsRange); + } + + public void ScrollToStart() + { + Control.LayoutManager.EnsureLayoutForCharacterRange(new NSRange(0, Control.TextStorage.Length)); + Control.ScrollRectToVisible(new CGRect(GetScrollX(), 0, 1, 1)); + } + + nfloat GetScrollX() => Control.UserInterfaceLayoutDirection switch + { + NSUserInterfaceLayoutDirection.RightToLeft => Control.Alignment switch + { + NSTextAlignment.Right => Control.Bounds.Left, + NSTextAlignment.Center => Control.Bounds.GetMidX() - Scroll.DocumentVisibleRect.Width / 2, + _ => Control.Bounds.Right, + }, + _ => Control.Alignment switch + { + NSTextAlignment.Right => Control.Bounds.Right, + NSTextAlignment.Center => Control.Bounds.GetMidX() - Scroll.DocumentVisibleRect.Width / 2, + _ => Control.Bounds.Left, + }, + }; - public void ScrollToEnd() => ScrollTo(Range.FromLength(TextLength, 0)); + public void ScrollToEnd() + { + Control.LayoutManager.EnsureLayoutForCharacterRange(new NSRange(0, Control.TextStorage.Length)); + Control.ScrollPoint(new CGPoint(GetScrollX(), Control.Bounds.Bottom)); + } } } \ No newline at end of file diff --git a/src/Eto.WinForms/Forms/Controls/TextAreaHandler.cs b/src/Eto.WinForms/Forms/Controls/TextAreaHandler.cs index b061d37d90..3b0e9bc285 100644 --- a/src/Eto.WinForms/Forms/Controls/TextAreaHandler.cs +++ b/src/Eto.WinForms/Forms/Controls/TextAreaHandler.cs @@ -213,12 +213,15 @@ public override bool ShouldBubbleEvent(swf.Message msg) return !intrinsicEvents.Contains((Win32.WM)msg.Msg) && base.ShouldBubbleEvent(msg); } + TextAlignment _textAlignment; + public TextAlignment TextAlignment { - get { return Control.SelectionAlignment.ToEto(); } + get => _textAlignment; set { if (value == TextAlignment) return; + _textAlignment = value; var sel = Selection; Control.SelectAll(); Control.SelectionAlignment = value.ToSWF(); @@ -277,18 +280,23 @@ public void ScrollTo(Range range) Win32.SendMessage(Control.Handle, Win32.WM.EM_SETSCROLLPOS, IntPtr.Zero, ref scrollPosition); } + Win32.SB GetScrollX() => Control.RightToLeft switch + { + swf.RightToLeft.Yes => Win32.SB.RIGHT, + _ => Win32.SB.LEFT + }; + public void ScrollToStart() { Win32.SendMessage(Control.Handle, Win32.WM.VSCROLL, (IntPtr)Win32.SB.TOP, IntPtr.Zero); - Win32.SendMessage(Control.Handle, Win32.WM.HSCROLL, (IntPtr)Win32.SB.LEFT, IntPtr.Zero); + Win32.SendMessage(Control.Handle, Win32.WM.HSCROLL, (IntPtr)GetScrollX(), IntPtr.Zero); } public void ScrollToEnd() { Win32.SendMessage(Control.Handle, Win32.WM.VSCROLL, (IntPtr)Win32.SB.BOTTOM, IntPtr.Zero); - Win32.SendMessage(Control.Handle, Win32.WM.HSCROLL, (IntPtr)Win32.SB.LEFT, IntPtr.Zero); + Win32.SendMessage(Control.Handle, Win32.WM.HSCROLL, (IntPtr)GetScrollX(), IntPtr.Zero); } - } } \ No newline at end of file diff --git a/src/Eto.Wpf/Forms/Controls/TextAreaHandler.cs b/src/Eto.Wpf/Forms/Controls/TextAreaHandler.cs index d8589a2010..7f7f3ac7d8 100755 --- a/src/Eto.Wpf/Forms/Controls/TextAreaHandler.cs +++ b/src/Eto.Wpf/Forms/Controls/TextAreaHandler.cs @@ -258,9 +258,33 @@ public virtual BorderType Border public abstract void ScrollTo(Range range); - public virtual void ScrollToEnd() => Control.ScrollToEnd(); + double GetScrollX() => Control.FlowDirection switch + { + sw.FlowDirection.RightToLeft => Control.HorizontalContentAlignment switch + { + sw.HorizontalAlignment.Right => 0, + sw.HorizontalAlignment.Center => (Control.ExtentWidth - Control.ViewportWidth) / 2, + _ => Control.ExtentWidth, + }, + _ => Control.HorizontalContentAlignment switch + { + sw.HorizontalAlignment.Right => Control.ExtentWidth, + sw.HorizontalAlignment.Center => (Control.ExtentWidth - Control.ViewportWidth) / 2, + _ => 0, + }, + }; + + public virtual void ScrollToEnd() + { + Control.ScrollToVerticalOffset(Control.ExtentHeight); + Control.ScrollToHorizontalOffset(GetScrollX()); + } - public virtual void ScrollToStart() => Control.ScrollToHome(); + public virtual void ScrollToStart() + { + Control.ScrollToVerticalOffset(0); + Control.ScrollToHorizontalOffset(GetScrollX()); + } public abstract int TextLength { get; } diff --git a/src/Eto/Forms/Controls/TextArea.cs b/src/Eto/Forms/Controls/TextArea.cs index 15c20a0a56..4be8efee7a 100644 --- a/src/Eto/Forms/Controls/TextArea.cs +++ b/src/Eto/Forms/Controls/TextArea.cs @@ -320,12 +320,12 @@ public void Append(string text, bool scrollToCursor = false) public void ScrollTo(Range range) => Handler.ScrollTo(range); /// - /// Scrolls to the start of the text in the text area. + /// Scrolls to the start of the text aligned on the same horizontal side as . /// public void ScrollToStart() => Handler.ScrollToStart(); /// - /// Scrolls to the end of the text in the text area. + /// Scrolls to the end of the text aligned on the same horizontal side as . /// public void ScrollToEnd() => Handler.ScrollToEnd(); diff --git a/test/Eto.Test/Handlers/TabControlHandler.cs b/test/Eto.Test/Handlers/TabControlHandler.cs index 115f30bffa..ccc9d5233d 100644 --- a/test/Eto.Test/Handlers/TabControlHandler.cs +++ b/test/Eto.Test/Handlers/TabControlHandler.cs @@ -30,7 +30,7 @@ public TabControlHandler() }, }; - ContentPanel = new Panel { BackgroundColor = Colors.White }; + ContentPanel = new Panel { BackgroundColor = SystemColors.ControlBackground }; var layout = new DynamicLayout { Padding = Padding.Empty, Spacing = Size.Empty }; layout.BeginHorizontal(); layout.Add(tabs); diff --git a/test/Eto.Test/LoremGenerator.cs b/test/Eto.Test/LoremGenerator.cs new file mode 100644 index 0000000000..5c2f77f22e --- /dev/null +++ b/test/Eto.Test/LoremGenerator.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Eto.Test +{ + public static class LoremGenerator + { + private static readonly string[] Words = new[] + { + "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "sed", "do", + "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua", "ut", + "enim", "ad", "minim", "veniam", "quis", "nostrud", "exercitation", "ullamco", "laboris", + "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat", "duis", "aute", "irure", "dolor", + "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", + "nulla", "pariatur", "excepteur", "sint", "occaecat", "cupidatat", "non", "proident", "sunt", + "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum" + }; + + public static string Generate(int wordCount, bool randomize = true) + { + var random = new Random(); + var result = new List(); + + for (int i = 0; i < wordCount; i++) + { + var word = Words[randomize ? random.Next(Words.Length) : i % Words.Length]; + result.Add(word); + } + + return string.Join(" ", result); + } + + public static string GenerateLines(int lineCount, int maxWordsPerLine, bool randomize = true) + { + return GenerateLines(lineCount, 0, maxWordsPerLine, randomize); + } + public static string GenerateLines(int lineCount, int minWordsPerLine, int maxWordsPerLine, bool randomize = true) + { + var random = new Random(); + var result = new List(); + + for (int i = 0; i < lineCount; i++) + { + var line = Generate(random.Next(minWordsPerLine, maxWordsPerLine), randomize); + result.Add(line); + } + + return string.Join("\n", result); + } + } +} \ No newline at end of file diff --git a/test/Eto.Test/Sections/Controls/LabelSection.cs b/test/Eto.Test/Sections/Controls/LabelSection.cs index cf737acb2c..6f996c5574 100644 --- a/test/Eto.Test/Sections/Controls/LabelSection.cs +++ b/test/Eto.Test/Sections/Controls/LabelSection.cs @@ -62,7 +62,7 @@ Control MiddleLabel() Text = "Middle Center Align", TextAlignment = TextAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - BackgroundColor = Colors.AliceBlue + BackgroundColor = Colors.Gray }; } @@ -73,7 +73,7 @@ Control BottomLabel() Text = "Bottom Center Align", TextAlignment = TextAlignment.Center, VerticalAlignment = VerticalAlignment.Bottom, - BackgroundColor = Colors.AliceBlue + BackgroundColor = Colors.Gray }; } @@ -125,7 +125,7 @@ Control WrapLabel() { var label = new Label { - Text = Utility.LoremTextWithTwoParagraphs + Text = LoremGenerator.GenerateLines(2, 50, 100) }; var wrapDropDown = new EnumDropDown(); diff --git a/test/Eto.Test/Sections/Controls/RichTextAreaSection.cs b/test/Eto.Test/Sections/Controls/RichTextAreaSection.cs index 7a84224914..57b0f98a98 100644 --- a/test/Eto.Test/Sections/Controls/RichTextAreaSection.cs +++ b/test/Eto.Test/Sections/Controls/RichTextAreaSection.cs @@ -5,7 +5,7 @@ public class RichTextAreaSection : Panel { public static string RtfString = "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1343\\cocoasubrtf160\r\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\r\n{\\colortbl;\\red255\\green255\\blue255;}\r\n\\margl1440\\margr1440\\vieww10800\\viewh8400\\viewkind0\r\n\\pard\\tx566\\tx1133\\tx1700\\tx2267\\tx2834\\tx3401\\tx3968\\tx4535\\tx5102\\tx5669\\tx6236\\tx6803\\pardirnatural\r\n\r\n\\f0\\fs24 \\cf0 This is some \r\n\\b bold\r\n\\b0 , \r\n\\i italic\r\n\\i0 , and \\ul underline\\ulnone text! \\\r\n\\\r\n\\pard\\tx566\\tx1133\\tx1700\\tx2267\\tx2834\\tx3401\\tx3968\\tx4535\\tx5102\\tx5669\\tx6236\\tx6803\\pardirnatural\\qr\r\n\\cf0 Some other text}"; - static string LastText = Utility.LoremTextWithTwoParagraphs; + static string LastText = LoremGenerator.GenerateLines(4, 50); public RichTextAreaSection() { diff --git a/test/Eto.Test/Sections/Controls/TextAreaSection.cs b/test/Eto.Test/Sections/Controls/TextAreaSection.cs index 1b3396f2ae..cecd92f09a 100644 --- a/test/Eto.Test/Sections/Controls/TextAreaSection.cs +++ b/test/Eto.Test/Sections/Controls/TextAreaSection.cs @@ -10,7 +10,7 @@ public TextAreaSection() Control Default() { - var text = new TextArea { Text = Utility.LoremTextWithTwoParagraphs }; + var text = new TextArea { Text = LoremGenerator.GenerateLines(3, 100) }; LogEvents(text); return new TableLayout diff --git a/test/Eto.Test/Sections/Drawing/FormattedTextSection.cs b/test/Eto.Test/Sections/Drawing/FormattedTextSection.cs index c11bce78b3..2af54d6fbd 100644 --- a/test/Eto.Test/Sections/Drawing/FormattedTextSection.cs +++ b/test/Eto.Test/Sections/Drawing/FormattedTextSection.cs @@ -23,7 +23,7 @@ public FormattedTextSection() var formattedText = new FormattedText { - Text = Utility.LoremTextWithTwoParagraphs, + Text = LoremGenerator.GenerateLines(2, 10, 30), MaximumSize = new SizeF(500, 80), Wrap = FormattedTextWrapMode.Word, Trimming = FormattedTextTrimming.CharacterEllipsis, @@ -34,7 +34,7 @@ public FormattedTextSection() var formattedTextWithNewLines = new FormattedText { - Text = Utility.LoremTextWithNewLines, + Text = LoremGenerator.GenerateLines(6, 10, 20), Wrap = FormattedTextWrapMode.Word, Trimming = FormattedTextTrimming.CharacterEllipsis, ForegroundBrush = Brushes.White, diff --git a/test/Eto.Test/UnitTests/Forms/Controls/LabelTests.cs b/test/Eto.Test/UnitTests/Forms/Controls/LabelTests.cs index fa7dd11f52..9db5ac371a 100644 --- a/test/Eto.Test/UnitTests/Forms/Controls/LabelTests.cs +++ b/test/Eto.Test/UnitTests/Forms/Controls/LabelTests.cs @@ -28,7 +28,7 @@ public void WrapModeNoneShouldAllowNewLines() { var label = new Label { - Text = Utility.LoremTextWithTwoParagraphs, + Text = LoremGenerator.GenerateLines(2, 50), Wrap = WrapMode.None }; var layout = new PixelLayout diff --git a/test/Eto.Test/UnitTests/Forms/Controls/TextAreaTests.cs b/test/Eto.Test/UnitTests/Forms/Controls/TextAreaTests.cs index 25a75e0243..1a4149706b 100644 --- a/test/Eto.Test/UnitTests/Forms/Controls/TextAreaTests.cs +++ b/test/Eto.Test/UnitTests/Forms/Controls/TextAreaTests.cs @@ -11,6 +11,28 @@ public class TextAreaTests : TextAreaTests