Skip to content
Open
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
14 changes: 13 additions & 1 deletion Source/HtmlRenderer/Core/Handlers/SelectionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,11 @@ private void HandleSelection(RControl control, RPoint loc, bool allowPartialSele
// if there is matching word
if (word != null)
{
var previousStart = _selectionStart;
var previousEnd = _selectionEnd;
var previousStartIndex = _selectionStartIndex;
var previousEndIndex = _selectionEndIndex;

if (_selectionStart == null)
{
// on start set the selection start word
Expand Down Expand Up @@ -476,7 +481,14 @@ private void HandleSelection(RControl control, RPoint loc, bool allowPartialSele

_cursorChanged = true;
control.SetCursorIBeam();
control.Invalidate();

// Most mouse moves stay within the same character, repainting the whole control for
// them only adds flicker, especially over remote desktop.
if (_selectionStart != previousStart || _selectionEnd != previousEnd
|| _selectionStartIndex != previousStartIndex || _selectionEndIndex != previousEndIndex)
{
control.Invalidate();
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using HtmlRenderer.IntegrationTest.TestSupport;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.Core.Dom;

namespace HtmlRenderer.IntegrationTest.Text;

/// <summary>
/// Dragging a selection repaints the control only when the selection changes, every extra repaint shows up
/// as flicker over remote desktop (#271).
/// </summary>
[DoNotParallelize]
[TestClass]
public sealed class SelectionInvalidateIntegrationTests
{
[TestMethod]
public void MouseMove_WithinTheSameCharacter_DoesNotRepaint()
{
var (root, container) = LayoutHarness.Layout(LayoutHarness.Wrap("<pre id='p'>first second third</pre>"));
var words = new List<CssRect>();
CollectWords(LayoutHarness.FindById(root, "p")!, words);
var first = words.First(w => w.Text == "first");
var third = words.First(w => w.Text == "third");
var control = new FakeControl(container.Adapter);

container.HandleMouseDown(control, new RPoint(first.Left + 1, first.Top + 2));
control.Invalidations = 0;
var target = new RPoint(third.Left + third.Width / 2, third.Top + 2);

container.HandleMouseMove(control, target);
Assert.AreEqual(1, control.Invalidations, "starting the selection must repaint");

container.HandleMouseMove(control, target);
container.HandleMouseMove(control, new RPoint(target.X + 0.1, target.Y + 1));
Assert.AreEqual(1, control.Invalidations, "moving within the same character must not repaint");

container.HandleMouseMove(control, new RPoint(third.Right - 1, target.Y));
Assert.AreEqual(2, control.Invalidations, "extending the selection must repaint");
}

private static void CollectWords(CssBox box, List<CssRect> words)
{
words.AddRange(box.Words);
foreach (var child in box.Boxes)
CollectWords(child, words);
}

/// <summary>A control with the left mouse button held down that counts its repaints.</summary>
private sealed class FakeControl(RAdapter adapter) : RControl(adapter)
{
private const double CharWidth = 8;

public int Invalidations { get; set; }

public override bool LeftMouseButton => true;
public override bool RightMouseButton => false;
public override RPoint MouseLocation => RPoint.Empty;
public override void SetCursorDefault() { }
public override void SetCursorHand() { }
public override void SetCursorIBeam() { }
public override void DoDragDropCopy(object dragDropData) { }

public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth)
{
charFit = Math.Min(str.Length, (int)(maxWidth / CharWidth));
charFitWidth = charFit * CharWidth;
}

public override void Invalidate() => Invalidations++;
}
}
Loading