1package vulkan
2
3import (
4 "image"
5 "image/color"
6 "testing"
7
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11// A tile and its lines are cut at TileArea, so a bottom layer panel under
12// them stays visible; a float there is drawn over it.
13func TestTileInsetKeepsBottomPanel(t *testing.T) {
14 r, err := New(64, 48)
15 if err != nil {
16 t.Skipf("Vulkan unavailable: %v", err)
17 }
18 defer r.Close()
19 red := color.RGBA{255, 0, 0, 255}
20 green := color.RGBA{0, 255, 0, 255}
21 blue := color.RGBA{0, 0, 255, 255}
22 contents := map[ports.WindowID]ports.SurfaceContent{1: solidContent(t, 64, 48, red), 2: solidContent(t, 64, 8, green), 3: solidContent(t, 8, 8, blue)}
23 scene := ports.Scene{Seq: 1, OutputWidth: 64, OutputHeight: 48, Background: "#000000", TileInset: ports.Insets{Bottom: 8},
24 Border: ports.Border{Width: 2, Inactive: "#ffffff"},
25 Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 48}}},
26 Separators: []ports.Separator{{Rect: ports.Rect{X: 30, W: 2, H: 48}}},
27 Layers: []ports.SceneLayer{{ID: 2, Rect: ports.Rect{Y: 40, W: 64, H: 8}, Layer: ports.LayerBottom}},
28 }
29 if err := render(r, scene, contents); err != nil {
30 t.Fatal(err)
31 }
32 pixels := readPixels(t, r)
33 white := color.RGBA{255, 255, 255, 255}
34 for point, want := range map[image.Point]color.RGBA{{10, 10}: red, {10, 39}: red, {10, 40}: green, {60, 47}: green, {30, 39}: white, {30, 44}: green} {
35 if got := pixels.RGBAAt(point.X, point.Y); got != want {
36 t.Errorf("pixel %v=%v, want %v", point, got, want)
37 }
38 }
39 scene.Seq++
40 scene.Windows = []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 48}}, {ID: 3, Rect: ports.Rect{Y: 40, W: 8, H: 8}, Floating: true}}
41 if err := render(r, scene, contents); err != nil {
42 t.Fatal(err)
43 }
44 if got := readPixels(t, r).RGBAAt(2, 44); got != blue {
45 t.Fatalf("float clipped at the panel: %v", got)
46 }
47}
48
49// A tile whose content changes damages only what is drawn of it, never the
50// panel under it.
51func TestTileDamageStopsAtPanel(t *testing.T) {
52 r, err := New(64, 48)
53 if err != nil {
54 t.Skipf("Vulkan unavailable: %v", err)
55 }
56 defer r.Close()
57 scene := ports.Scene{Seq: 1, Scale: 1, OutputWidth: 64, OutputHeight: 48, Background: "#000000", TileInset: ports.Insets{Bottom: 8},
58 Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 48}}}}
59 c := solidContent(t, 64, 48, color.RGBA{255, 0, 0, 255})
60 c.ID, c.Seq = 1, 2 // the target holds Seq 1, with no damage history
61 bounds := image.Rect(0, 0, 64, 48)
62 held := &target{valid: true, sceneSeq: scene.Seq, scene: scene, windows: map[ports.WindowID]heldWindow{1: {seq: 1, rect: image.Rect(0, 0, 64, 40)}}}
63 dmg := r.frameDamage(held, scene, bounds)
64 _ = r.draws(scene, map[ports.WindowID]ports.SurfaceContent{1: c}, dmg)
65 dmg.finish()
66 if dmg.all() || dmg.area != image.Rect(0, 0, 64, 40) {
67 t.Fatalf("damage %v (all %v), want the tile above the panel", dmg.area, dmg.all())
68 }
69}
70
71func TestWorkspaceClipWindowsPopupsAndLayers(t *testing.T) {
72 r, err := New(64, 48)
73 if err != nil {
74 t.Skipf("Vulkan unavailable: %v", err)
75 }
76 defer r.Close()
77 red := color.RGBA{255, 0, 0, 255}
78 green := color.RGBA{0, 255, 0, 255}
79 contents := map[ports.WindowID]ports.SurfaceContent{1: solidContent(t, 64, 48, red), 2: solidContent(t, 8, 8, green)}
80 clip := ports.Rect{X: 16, Y: 12, W: 32, H: 24}
81 scene := ports.Scene{Seq: 1, OutputWidth: 64, OutputHeight: 48, Background: "#000000", WorkspaceClip: clip,
82 Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 48}}},
83 Layers: []ports.SceneLayer{{ID: 2, Rect: ports.Rect{W: 8, H: 8}, Layer: ports.LayerTop}},
84 }
85 if err := render(r, scene, contents); err != nil {
86 t.Fatal(err)
87 }
88 pixels := readPixels(t, r)
89 for point, want := range map[image.Point]color.RGBA{{0, 0}: green, {10, 10}: {0, 0, 0, 255}, {16, 12}: red, {47, 35}: red, {48, 36}: {0, 0, 0, 255}} {
90 if got := pixels.RGBAAt(point.X, point.Y); got != want {
91 t.Errorf("pixel %v=%v, want %v", point, got, want)
92 }
93 }
94 // Content-only damage at the same scene sequence must remain clipped.
95 changed := contents[1]
96 changed.Seq++
97 changed.DamageHistory = []ports.SeqDamage{{Seq: changed.Seq, Full: true}}
98 if _, err := changed.SHM.File.WriteAt([]byte{255, 255, 255, 255}, 0); err != nil {
99 t.Fatal(err)
100 }
101 contents[1] = changed
102 if err := render(r, scene, contents); err != nil {
103 t.Fatal(err)
104 }
105 if got := readPixels(t, r).RGBAAt(10, 10); got != (color.RGBA{0, 0, 0, 255}) {
106 t.Fatalf("damage escaped clip: %v", got)
107 }
108 // A popup from a workspace surface is clipped too; input popups over
109 // layers retain output-wide bounds.
110 scene.Seq++
111 scene.Windows = []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 48}, Popup: true}}
112 if err := render(r, scene, contents); err != nil {
113 t.Fatal(err)
114 }
115 if got := readPixels(t, r).RGBAAt(10, 10); got != (color.RGBA{0, 0, 0, 255}) {
116 t.Fatalf("popup escaped clip: %v", got)
117 }
118 // Popups above layers belong to output-wide controls, not the workspace.
119 // Scenes are immutable snapshots (the target holds the previous one):
120 // a changed window goes in a fresh slice, as core publishes it.
121 scene.Seq++
122 scene.Windows = []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 48}, Popup: true, OverLayers: true}}
123 if err := render(r, scene, contents); err != nil {
124 t.Fatal(err)
125 }
126 if got := readPixels(t, r).RGBAAt(10, 10); got != red {
127 t.Fatalf("output-wide popup clipped: %v", got)
128 }
129 scene.Windows = []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 48}, Popup: true}}
130 // Switching back to inherited geometry repaints the margins.
131 scene.Seq++
132 scene.WorkspaceClip = ports.Rect{}
133 if err := render(r, scene, contents); err != nil {
134 t.Fatal(err)
135 }
136 if got := readPixels(t, r).RGBAAt(10, 10); got != red {
137 t.Fatalf("stale clip after inheritance: %v", got)
138 }
139}