Skip to content

Commit 10774b8

Browse files
authored
Repaint when the picture changed and nothing else did (#842)
* Repaint when the picture changed and nothing else did The Windows head repaints only when the new screen differs from the last one, because ScreenBuilder allocates a fresh Screen every frame and record equality would repaint sixty times a second. That comparison never looked at the image pane, and an image pane said only where the file was and how big it was. An image side's rows are format, dimensions and byte count, so a re-run that rewrites a received image at the same size changes nothing in the model - for BMP, being uncompressed, that is every re-run. Apply returned before invalidating, the canvas never repainted, and the pane kept the previous picture. The stamp based freshness check in ImageCache was never even consulted, since nothing asked it to paint. ImagePane carries the content hash the model already had, and the pane comparison includes the image. The hash is not for drawing - a head reads the file - but for telling one picture from another at the same path and size. * Say the hash in the pane the test asserts on
1 parent d68cc9c commit 10774b8

5 files changed

Lines changed: 74 additions & 5 deletions

File tree

src/DiffEngineViewer.Tests/ImageScreenTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ public Task DeleteInQueue() =>
9999
public async Task PanesCarryThePicture()
100100
{
101101
var screen = ScreenBuilder.Build(State(Received(), Expected()));
102-
await Assert.That(screen.Left.Image).IsEqualTo(new("temp/sample.received.png", 800, 600));
103-
await Assert.That(screen.Right.Image).IsEqualTo(new("code/sample.verified.png", 800, 600));
102+
// The hash the side carried, which is how a head tells one picture from another at the
103+
// same path and size - and how the Windows head knows to repaint for it
104+
await Assert.That(screen.Left.Image).IsEqualTo(new("temp/sample.received.png", 800, 600, "0A"));
105+
await Assert.That(screen.Right.Image).IsEqualTo(new("code/sample.verified.png", 800, 600, "0A"));
104106
}
105107

106108
[Test]
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/// <summary>
2+
/// Whether two panes are the same pane, which is what decides whether the window repaints.
3+
/// ScreenBuilder allocates a fresh Screen every frame, so record equality would report a change
4+
/// sixty times a second and the comparison is by hand.
5+
/// </summary>
6+
public class PaneChangeTests
7+
{
8+
/// <summary>
9+
/// The rows an image side shows are format, dimensions and byte count, and a re-run that
10+
/// rewrites a received image at the same size changes none of them - for BMP, which is
11+
/// uncompressed, that is every re-run. So nothing about the screen differed, Apply returned
12+
/// before repainting, and the pane kept the previous picture while the rows beside it
13+
/// described the new one.
14+
/// </summary>
15+
[Test]
16+
public async Task A_picture_that_changed_is_not_the_same_pane()
17+
{
18+
var before = ImagePane("A1B2");
19+
var after = ImagePane("C3D4");
20+
21+
await Assert.That(ViewerForm.Same(before, after)).IsFalse();
22+
}
23+
24+
[Test]
25+
public async Task A_picture_that_did_not_change_is_the_same_pane() =>
26+
await Assert.That(ViewerForm.Same(ImagePane("A1B2"), ImagePane("A1B2"))).IsTrue();
27+
28+
[Test]
29+
public async Task A_picture_replaced_by_one_of_another_size_is_not_the_same_pane()
30+
{
31+
var before = ImagePane("A1B2");
32+
var after = before with
33+
{
34+
Image = new("sample.received.png", 20, 10, "A1B2")
35+
};
36+
37+
await Assert.That(ViewerForm.Same(before, after)).IsFalse();
38+
}
39+
40+
[Test]
41+
public async Task Text_panes_are_unaffected()
42+
{
43+
var pane = new Pane("received", [new(1, RowKind.Unchanged, "one")], 0, 1);
44+
45+
await Assert.That(ViewerForm.Same(pane, pane with { })).IsTrue();
46+
await Assert.That(ViewerForm.Same(pane, pane with { ScrollTop = 1 })).IsFalse();
47+
}
48+
49+
static Pane ImagePane(string hash) =>
50+
new(
51+
"received",
52+
[],
53+
0,
54+
0,
55+
new("sample.received.png", 10, 10, hash));
56+
}

src/DiffEngineViewer.Windows/ViewerForm.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,14 @@ left is null
422422
left.Row == right.Row &&
423423
left.Labels.SequenceEqual(right.Labels);
424424

425-
static bool Same(Pane left, Pane right) =>
425+
internal static bool Same(Pane left, Pane right) =>
426426
left.Header == right.Header &&
427427
left.ScrollTop == right.ScrollTop &&
428428
left.TotalRows == right.TotalRows &&
429+
// Records all the way down, so this compares the path, the size and the content stamp. A
430+
// re-run that rewrites a received image at the same size changes nothing else about the
431+
// screen - the rows say format, dimensions and byte count, and for BMP those hold - so
432+
// without it Apply returned before repainting and the pane kept the previous picture.
433+
left.Image == right.Image &&
429434
left.Rows.SequenceEqual(right.Rows);
430435
}

src/DiffEngineViewer/Model/ImagePane.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,10 @@
1313
/// worth trying.
1414
/// </para>
1515
/// </summary>
16-
record ImagePane(string Path, int Width, int Height);
16+
/// <param name="Hash">
17+
/// What the file held when the model was built, so a head can tell one picture from another at the
18+
/// same path and size. Not for drawing - the head reads the file - but for deciding whether what
19+
/// is on screen is still this. A re-run that rewrites a received image at the same dimensions
20+
/// changes nothing else in the model, and for BMP that is every re-run.
21+
/// </param>
22+
record ImagePane(string Path, int Width, int Height, string? Hash);

src/DiffEngineViewer/ScreenBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ static Pane BuildPane(
9595
return null;
9696
}
9797

98-
return new(file.Path, header.Width, header.Height);
98+
return new(file.Path, header.Width, header.Height, file.Hash);
9999
}
100100

101101
static IReadOnlyList<QueueItem> BuildQueue(SessionState state, int body, out int top)

0 commit comments

Comments
 (0)