1package vulkan
2
3import (
4 "image"
5 "math"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9
10 "github.com/bnema/neferwl/internal/ports"
11)
12
13func TestHintLabelsDrawnAfterIndicators(t *testing.T) {
14 r, err := New(256, 256)
15 if err != nil {
16 t.Skipf("Vulkan unavailable: %v", err)
17 }
18 defer r.Close()
19 for _, scale := range []int{1, 2} {
20 s := ports.Scene{Scale: float64(scale),
21 CaptureIndicators: []ports.CaptureIndicator{{Rect: ports.Rect{W: 64, H: 64}}},
22 HintLabels: []ports.HintLabel{
23 {Rect: ports.Rect{X: 80, Y: 90}, Text: "f"},
24 {Rect: ports.Rect{X: 100, Y: 110}, Text: "jk", Typed: 1},
25 }}
26 bare := s
27 bare.HintLabels = nil
28 before := len(r.draws(bare, nil, newDamage(&target{}, bare, image.Rect(0, 0, 256, 256))))
29 ds := r.draws(s, nil, newDamage(&target{}, s, image.Rect(0, 0, 256, 256)))
30 require.Len(t, ds, before+2, "one draw per label, after the indicator's")
31 for i, tl := range []image.Point{{80 * scale, 90 * scale}, {100 * scale, 110 * scale}} {
32 d := ds[before+i]
33 require.NotNil(t, d.shm)
34 lw, lh := int(d.pc.rect[2]-d.pc.rect[0]), int(d.pc.rect[3]-d.pc.rect[1])
35 inset := int(math.Round(0.25 * float64(lh)))
36 require.Equal(t, int32(tl.X-lw+inset), d.pc.rect[0])
37 require.Equal(t, int32(tl.Y-lh+inset), d.pc.rect[1])
38 require.Greater(t, d.pc.rect[2], d.pc.rect[0])
39 require.Greater(t, d.pc.rect[3], d.pc.rect[1])
40 require.Equal(t, float32(1), d.pc.color[3])
41 }
42 }
43}
44
45func TestHintLabelsAnchoredAndClampedAtTopLeft(t *testing.T) {
46 r, err := New(128, 128)
47 if err != nil {
48 t.Skipf("Vulkan unavailable: %v", err)
49 }
50 defer r.Close()
51 // A target at the origin puts the anchor off-screen: clamped to 0,0.
52 s := ports.Scene{Scale: 1, HintLabels: []ports.HintLabel{{Rect: ports.Rect{X: 0, Y: 0}, Text: "f"}, {Rect: ports.Rect{X: 60, Y: 60}, Text: "f"}}}
53 ds := r.draws(s, nil, newDamage(&target{}, s, image.Rect(0, 0, 128, 128)))
54 require.Len(t, ds, 2)
55 require.Equal(t, int32(0), ds[0].pc.rect[0])
56 require.Equal(t, int32(0), ds[0].pc.rect[1])
57 lw, lh := int(ds[1].pc.rect[2]-ds[1].pc.rect[0]), int(ds[1].pc.rect[3]-ds[1].pc.rect[1])
58 inset := int(math.Round(0.25 * float64(lh)))
59 require.Equal(t, int32(60-lw+inset), ds[1].pc.rect[0])
60 require.Equal(t, int32(60-lh+inset), ds[1].pc.rect[1])
61 require.Less(t, int(ds[1].pc.rect[2]), 60+lw, "label sits left of the target, mostly outside it")
62}
63
64func TestHintLabelsAbsentDrawNothing(t *testing.T) {
65 r := &Renderer{width: 64, height: 64}
66 s := ports.Scene{Scale: 1}
67 require.Empty(t, r.draws(s, nil, newDamage(&target{}, s, image.Rect(0, 0, 64, 64))))
68}
69
70func TestHintLabelsClampedInsideOutput(t *testing.T) {
71 r, err := New(128, 128)
72 if err != nil {
73 t.Skipf("Vulkan unavailable: %v", err)
74 }
75 defer r.Close()
76 s := ports.Scene{Scale: 1, HintStyle: ports.HintStyle{Size: 40}, HintLabels: []ports.HintLabel{{Rect: ports.Rect{X: 127, Y: 127}, Text: "ff"}}}
77 ds := r.draws(s, nil, newDamage(&target{}, s, image.Rect(0, 0, 128, 128)))
78 require.Len(t, ds, 1)
79 require.LessOrEqual(t, ds[0].pc.rect[2], int32(128))
80 require.LessOrEqual(t, ds[0].pc.rect[3], int32(128))
81}
82
83func TestHintLabelsRenderAndUploadOnce(t *testing.T) {
84 r, err := New(128, 128)
85 if err != nil {
86 t.Skipf("Vulkan unavailable: %v", err)
87 }
88 defer r.Close()
89 s := ports.Scene{Scale: 1, Seq: 1, HintLabels: []ports.HintLabel{{Rect: ports.Rect{X: 4, Y: 4}, Text: "fj"}}}
90 require.NoError(t, render(r, s, nil))
91 first := r.copied
92 require.Positive(t, first)
93 s.Seq = 2
94 require.NoError(t, render(r, s, nil))
95 require.Equal(t, first, r.copied, "an unchanged label is not uploaded again")
96 s.HintStyle.Size = 26
97 s.Seq++
98 require.NoError(t, render(r, s, nil))
99 require.Greater(t, r.copied, first, "size reload uploads a new raster")
100 sized := r.copied
101 s.HintStyle.Background = "#89b4fa"
102 s.Seq++
103 require.NoError(t, render(r, s, nil))
104 require.Greater(t, r.copied, sized, "colour reload uploads a new raster")
105}
106
107func TestHintSceneDeltaOnlyHintLabelsChange(t *testing.T) {
108 phys := func(r ports.Rect) image.Rectangle { return image.Rect(r.X, r.Y, r.X+r.W, r.Y+r.H) }
109 base := ports.Scene{Scale: 1, OutputWidth: 100, OutputHeight: 100}
110 with := base
111 with.HintLabels = []ports.HintLabel{{Rect: ports.Rect{X: 1, Y: 1}, Text: "f"}}
112 _, ok := sceneDelta(base, with, phys, 1<<30)
113 require.False(t, ok, "labels appearing redraw the output")
114 typed := with
115 typed.HintLabels = []ports.HintLabel{{Rect: ports.Rect{X: 1, Y: 1}, Text: "f", Typed: 1}}
116 _, ok = sceneDelta(with, typed, phys, 1<<30)
117 require.False(t, ok)
118 _, ok = sceneDelta(with, with, phys, 1<<30)
119 require.True(t, ok, "same labels, no damage")
120}
121
122func TestHintLabelsHeldByTarget(t *testing.T) {
123 s := ports.Scene{Seq: 1, HintLabels: []ports.HintLabel{{Text: "f"}}}
124 tg := &target{}
125 tg.hold(s, &damageRegion{drawn: map[ports.WindowID]heldWindow{}})
126 s.HintLabels[0].Text = "j"
127 require.Equal(t, "f", tg.scene.HintLabels[0].Text, "the held scene owns its labels")
128}
129
130func TestHintStyleChangesSceneDamage(t *testing.T) {
131 old := ports.Scene{Scale: 1, HintLabels: []ports.HintLabel{{Text: "f"}}}
132 changed := old
133 changed.HintStyle.Size = 24
134 require.False(t, old.SameAs(changed))
135 _, partial := sceneDelta(old, changed, func(r ports.Rect) image.Rectangle { return image.Rect(r.X, r.Y, r.X+r.W, r.Y+r.H) }, 10)
136 require.False(t, partial)
137}