fix(scene): carry an animating window's content scale as Zoom, apart from Preview
publish copied the derived zoom into SceneWindow.Preview, which the vulkan walk reads as "overview card" (opensFloats): an appearing or leaving float stopped opening the floats, so the veil went missing and popped back and the tile lines were painted over the fading float. SceneWindow.Zoom (15 fields) is the content scale of a window whose frame animates; the walk uses it over Preview for content only, opensFloats keeps reading Preview. DRM scanout and the overlay plane refuse Zoom > 0 like Preview. sceneDelta covers it by struct equality (the zoomed content stays inside Rect). Walk test: a zoomed float with scene.Dim and separators paints the veil and the tile lines under it exactly as unzoomed; a Preview card still does not.
- commit
- a46389e57a40eb9e5f73b1cebcb7dc246dfcf420
- author
- brice · · 2026-10-04 22:34 UTC
- parent
- 8ecc5f8
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
9 files · +78 -6
M internal/adapters/drm/overlay.go +1 -1
| @@ -85,7 +85,7 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo | |||
| 85 | 85 | // window (a peeking stashed one) needs the veil drawn over it, a | |
| 86 | 86 | // fading one its translucency, and one with the focus effect its | |
| 87 | 87 | // lift; an overview preview is drawn smaller than its buffer. | |
| 88 | - | if w.Dim <= 0 && w.Fade <= 0 && w.FocusEffect <= 0 && w.Preview <= 0 && c.DMABuf != nil && !isYUVFormat(c.DMABuf.Format) && c.Opaque && len(c.Children) == 0 && c.Transform == 0 && !cropped(c) { | |
| 88 | + | if w.Dim <= 0 && w.Fade <= 0 && w.Zoom <= 0 && w.FocusEffect <= 0 && w.Preview <= 0 && c.DMABuf != nil && !isYUVFormat(c.DMABuf.Format) && c.Opaque && len(c.Children) == 0 && c.Transform == 0 && !cropped(c) { | |
| 89 | 89 | if m, why := planeColor(c.Color, c.DMABuf.Format, c.Opaque, hdrOn, pipeline); why == "" { | |
| 90 | 90 | pick, mode = w, m | |
| 91 | 91 | continue | |
| @@ -85,7 +85,7 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo | |||||
| 85 | // window (a peeking stashed one) needs the veil drawn over it, a | 85 | // window (a peeking stashed one) needs the veil drawn over it, a | ||
| 86 | // fading one its translucency, and one with the focus effect its | 86 | // fading one its translucency, and one with the focus effect its | ||
| 87 | // lift; an overview preview is drawn smaller than its buffer. | 87 | // lift; an overview preview is drawn smaller than its buffer. | ||
| 88 | - | if w.Dim <= 0 && w.Fade <= 0 && w.FocusEffect <= 0 && w.Preview <= 0 && c.DMABuf != nil && !isYUVFormat(c.DMABuf.Format) && c.Opaque && len(c.Children) == 0 && c.Transform == 0 && !cropped(c) { | 88 | + | if w.Dim <= 0 && w.Fade <= 0 && w.Zoom <= 0 && w.FocusEffect <= 0 && w.Preview <= 0 && c.DMABuf != nil && !isYUVFormat(c.DMABuf.Format) && c.Opaque && len(c.Children) == 0 && c.Transform == 0 && !cropped(c) { |
| 89 | if m, why := planeColor(c.Color, c.DMABuf.Format, c.Opaque, hdrOn, pipeline); why == "" { | 89 | if m, why := planeColor(c.Color, c.DMABuf.Format, c.Opaque, hdrOn, pipeline); why == "" { | ||
| 90 | pick, mode = w, m | 90 | pick, mode = w, m | ||
| 91 | continue | 91 | continue | ||
M internal/adapters/drm/overlay_test.go +6 -0
| @@ -119,6 +119,12 @@ func TestOverlayCandidate(t *testing.T) { | |||
| 119 | 119 | if _, _, _, reason := overlayCandidate(s, c, false, nil); reason != "no_candidate" { | |
| 120 | 120 | t.Fatalf("fading window reason %q", reason) | |
| 121 | 121 | } | |
| 122 | + | // A zoomed (animating) window is drawn smaller than its buffer. | |
| 123 | + | s, _ = overlayScene() | |
| 124 | + | s.Windows[len(s.Windows)-1].Zoom = 0.9 | |
| 125 | + | if _, _, _, reason := overlayCandidate(s, c, false, nil); reason != "no_candidate" { | |
| 126 | + | t.Fatalf("zoomed window reason %q", reason) | |
| 127 | + | } | |
| 122 | 128 | // A window with the focus effect needs it composed. | |
| 123 | 129 | s, _ = overlayScene() | |
| 124 | 130 | s.Windows[len(s.Windows)-1].FocusEffect = 0.05 | |
| @@ -119,6 +119,12 @@ func TestOverlayCandidate(t *testing.T) { | |||||
| 119 | if _, _, _, reason := overlayCandidate(s, c, false, nil); reason != "no_candidate" { | 119 | if _, _, _, reason := overlayCandidate(s, c, false, nil); reason != "no_candidate" { | ||
| 120 | t.Fatalf("fading window reason %q", reason) | 120 | t.Fatalf("fading window reason %q", reason) | ||
| 121 | } | 121 | } | ||
| 122 | + | // A zoomed (animating) window is drawn smaller than its buffer. | |||
| 123 | + | s, _ = overlayScene() | |||
| 124 | + | s.Windows[len(s.Windows)-1].Zoom = 0.9 | |||
| 125 | + | if _, _, _, reason := overlayCandidate(s, c, false, nil); reason != "no_candidate" { | |||
| 126 | + | t.Fatalf("zoomed window reason %q", reason) | |||
| 127 | + | } | |||
| 122 | // A window with the focus effect needs it composed. | 128 | // A window with the focus effect needs it composed. | ||
| 123 | s, _ = overlayScene() | 129 | s, _ = overlayScene() | ||
| 124 | s.Windows[len(s.Windows)-1].FocusEffect = 0.05 | 130 | s.Windows[len(s.Windows)-1].FocusEffect = 0.05 | ||
M internal/adapters/drm/scanout.go +1 -1
| @@ -115,7 +115,7 @@ func scanoutCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo | |||
| 115 | 115 | // Its veil is composed over it: the plane would show it bright. | |
| 116 | 116 | return ports.SurfaceContent{}, "dim" | |
| 117 | 117 | } | |
| 118 | - | if full.Fade > 0 || full.Preview > 0 { | |
| 118 | + | if full.Fade > 0 || full.Preview > 0 || full.Zoom > 0 { | |
| 119 | 119 | // A fading or zoomed window is composed: the plane would show it | |
| 120 | 120 | // whole and opaque. | |
| 121 | 121 | return ports.SurfaceContent{}, "window_fade" | |
| @@ -115,7 +115,7 @@ func scanoutCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo | |||||
| 115 | // Its veil is composed over it: the plane would show it bright. | 115 | // Its veil is composed over it: the plane would show it bright. | ||
| 116 | return ports.SurfaceContent{}, "dim" | 116 | return ports.SurfaceContent{}, "dim" | ||
| 117 | } | 117 | } | ||
| 118 | - | if full.Fade > 0 || full.Preview > 0 { | 118 | + | if full.Fade > 0 || full.Preview > 0 || full.Zoom > 0 { |
| 119 | // A fading or zoomed window is composed: the plane would show it | 119 | // A fading or zoomed window is composed: the plane would show it | ||
| 120 | // whole and opaque. | 120 | // whole and opaque. | ||
| 121 | return ports.SurfaceContent{}, "window_fade" | 121 | return ports.SurfaceContent{}, "window_fade" | ||
M internal/adapters/drm/scanout_test.go +2 -1
| @@ -28,7 +28,8 @@ func TestScanoutCandidate(t *testing.T) { | |||
| 28 | 28 | {"rotated output", func(s *ports.Scene) { s.Transform = 1 }, nil, "output_transform"}, | |
| 29 | 29 | {"dimmed", func(s *ports.Scene) { s.Windows[0].Dim = 0.5 }, nil, "dim"}, | |
| 30 | 30 | {"window fade", func(s *ports.Scene) { s.Windows[0].Fade = 0.5 }, nil, "window_fade"}, | |
| 31 | - | {"window zoom", func(s *ports.Scene) { s.Windows[0].Preview = 0.9 }, nil, "window_fade"}, | |
| 31 | + | {"window preview", func(s *ports.Scene) { s.Windows[0].Preview = 0.9 }, nil, "window_fade"}, | |
| 32 | + | {"window zoom", func(s *ports.Scene) { s.Windows[0].Zoom = 0.9 }, nil, "window_fade"}, | |
| 32 | 33 | {"faded", nil, func(c *ports.SurfaceContent) { c.Fade = 0.5 }, "fade"}, | |
| 33 | 34 | {"tiled", func(s *ports.Scene) { s.Windows[0].Fullscreen = false }, nil, "other_windows"}, | |
| 34 | 35 | {"float above", func(s *ports.Scene) { | |
| @@ -28,7 +28,8 @@ func TestScanoutCandidate(t *testing.T) { | |||||
| 28 | {"rotated output", func(s *ports.Scene) { s.Transform = 1 }, nil, "output_transform"}, | 28 | {"rotated output", func(s *ports.Scene) { s.Transform = 1 }, nil, "output_transform"}, | ||
| 29 | {"dimmed", func(s *ports.Scene) { s.Windows[0].Dim = 0.5 }, nil, "dim"}, | 29 | {"dimmed", func(s *ports.Scene) { s.Windows[0].Dim = 0.5 }, nil, "dim"}, | ||
| 30 | {"window fade", func(s *ports.Scene) { s.Windows[0].Fade = 0.5 }, nil, "window_fade"}, | 30 | {"window fade", func(s *ports.Scene) { s.Windows[0].Fade = 0.5 }, nil, "window_fade"}, | ||
| 31 | - | {"window zoom", func(s *ports.Scene) { s.Windows[0].Preview = 0.9 }, nil, "window_fade"}, | 31 | + | {"window preview", func(s *ports.Scene) { s.Windows[0].Preview = 0.9 }, nil, "window_fade"}, |
| 32 | + | {"window zoom", func(s *ports.Scene) { s.Windows[0].Zoom = 0.9 }, nil, "window_fade"}, | |||
| 32 | {"faded", nil, func(c *ports.SurfaceContent) { c.Fade = 0.5 }, "fade"}, | 33 | {"faded", nil, func(c *ports.SurfaceContent) { c.Fade = 0.5 }, "fade"}, | ||
| 33 | {"tiled", func(s *ports.Scene) { s.Windows[0].Fullscreen = false }, nil, "other_windows"}, | 34 | {"tiled", func(s *ports.Scene) { s.Windows[0].Fullscreen = false }, nil, "other_windows"}, | ||
| 34 | {"float above", func(s *ports.Scene) { | 35 | {"float above", func(s *ports.Scene) { | ||
M internal/adapters/vulkan/damage_delta_test.go +1 -1
| @@ -26,7 +26,7 @@ func TestSceneFieldCount(t *testing.T) { | |||
| 26 | 26 | // sceneDelta compares windows by struct equality, so a new field is covered | |
| 27 | 27 | // as long as it only changes where or how that window draws (inside Rect). | |
| 28 | 28 | func TestSceneWindowFieldCount(t *testing.T) { | |
| 29 | - | const fields = 14 | |
| 29 | + | const fields = 15 | |
| 30 | 30 | assert.Equal(t, fields, len(reflect.VisibleFields(reflect.TypeOf(ports.SceneWindow{}))), | |
| 31 | 31 | "SceneWindow fields changed: check sceneDelta (a field that draws outside Rect needs its own rule), then this count") | |
| 32 | 32 | } | |
| @@ -26,7 +26,7 @@ func TestSceneFieldCount(t *testing.T) { | |||||
| 26 | // sceneDelta compares windows by struct equality, so a new field is covered | 26 | // sceneDelta compares windows by struct equality, so a new field is covered | ||
| 27 | // as long as it only changes where or how that window draws (inside Rect). | 27 | // as long as it only changes where or how that window draws (inside Rect). | ||
| 28 | func TestSceneWindowFieldCount(t *testing.T) { | 28 | func TestSceneWindowFieldCount(t *testing.T) { | ||
| 29 | - | const fields = 14 | 29 | + | const fields = 15 |
| 30 | assert.Equal(t, fields, len(reflect.VisibleFields(reflect.TypeOf(ports.SceneWindow{}))), | 30 | assert.Equal(t, fields, len(reflect.VisibleFields(reflect.TypeOf(ports.SceneWindow{}))), | ||
| 31 | "SceneWindow fields changed: check sceneDelta (a field that draws outside Rect needs its own rule), then this count") | 31 | "SceneWindow fields changed: check sceneDelta (a field that draws outside Rect needs its own rule), then this count") | ||
| 32 | } | 32 | } | ||
M internal/adapters/vulkan/scene.go +5 -1
| @@ -231,7 +231,11 @@ func (w *sceneWalk) windows() { | |||
| 231 | 231 | // Until its first buffer, a window shows the background: no flash. | |
| 232 | 232 | w.fill(body, parseColor(w.s.Background)) | |
| 233 | 233 | if !content.Empty() { | |
| 234 | - | if win.Preview > 0 { | |
| 234 | + | // An animating frame zooms its content (Zoom); a card draws | |
| 235 | + | // at its Preview. | |
| 236 | + | if win.Zoom > 0 { | |
| 237 | + | w.zoom = win.Zoom | |
| 238 | + | } else if win.Preview > 0 { | |
| 235 | 239 | w.zoom = win.Preview | |
| 236 | 240 | } | |
| 237 | 241 | w.pulse = float32(max(0, min(win.FocusEffect, 1))) | |
| @@ -231,7 +231,11 @@ func (w *sceneWalk) windows() { | |||||
| 231 | // Until its first buffer, a window shows the background: no flash. | 231 | // Until its first buffer, a window shows the background: no flash. | ||
| 232 | w.fill(body, parseColor(w.s.Background)) | 232 | w.fill(body, parseColor(w.s.Background)) | ||
| 233 | if !content.Empty() { | 233 | if !content.Empty() { | ||
| 234 | - | if win.Preview > 0 { | 234 | + | // An animating frame zooms its content (Zoom); a card draws |
| 235 | + | // at its Preview. | |||
| 236 | + | if win.Zoom > 0 { | |||
| 237 | + | w.zoom = win.Zoom | |||
| 238 | + | } else if win.Preview > 0 { | |||
| 235 | w.zoom = win.Preview | 239 | w.zoom = win.Preview | ||
| 236 | } | 240 | } | ||
| 237 | w.pulse = float32(max(0, min(win.FocusEffect, 1))) | 241 | w.pulse = float32(max(0, min(win.FocusEffect, 1))) | ||
M internal/adapters/vulkan/scene_fade_test.go +55 -0
| @@ -86,3 +86,58 @@ func TestSceneWindowFadeHDR(t *testing.T) { | |||
| 86 | 86 | t.Fatalf("hdr faded white = %v, want rgb %v alpha 0.5", c, want) | |
| 87 | 87 | } | |
| 88 | 88 | } | |
| 89 | + | ||
| 90 | + | // An appearing or leaving float carries a Zoom, not a Preview: it still | |
| 91 | + | // opens the floats, so the tile lines and the veil are painted under it, | |
| 92 | + | // exactly as for the same float unzoomed; only its content draw shrinks. | |
| 93 | + | func TestSceneZoomedFloatOpensFloats(t *testing.T) { | |
| 94 | + | r := &Renderer{width: 80, height: 60} | |
| 95 | + | scene := func(zoom float64) ports.Scene { | |
| 96 | + | return ports.Scene{ | |
| 97 | + | Dim: 0.4, Background: "#000000", Border: ports.Border{Width: 1, Active: "#ffffff", Inactive: "#808080"}, | |
| 98 | + | Windows: []ports.SceneWindow{ | |
| 99 | + | {ID: 1, Rect: ports.Rect{W: 80, H: 60}}, | |
| 100 | + | {ID: 2, Floating: true, Rect: ports.Rect{X: 20, Y: 10, W: 40, H: 40}, Inset: ports.SideAll, Fade: 0.5, Zoom: zoom}, | |
| 101 | + | }, | |
| 102 | + | Separators: []ports.Separator{ | |
| 103 | + | {Rect: ports.Rect{X: 40, W: 1, H: 60}}, | |
| 104 | + | {Rect: ports.Rect{X: 20, Y: 10, W: 1, H: 40}, Window: 2}, | |
| 105 | + | }, | |
| 106 | + | } | |
| 107 | + | } | |
| 108 | + | contents := map[ports.WindowID]ports.SurfaceContent{ | |
| 109 | + | 2: {ID: 2, Width: 1, Height: 1, LogicalW: 38, LogicalH: 38, Solid: &ports.SolidColor{R: 1, G: 1, B: 1, A: 1}}, | |
| 110 | + | } | |
| 111 | + | walk := func(s ports.Scene) []draw { | |
| 112 | + | return slices.Clone(r.draws(s, contents, newDamage(&target{}, s, image.Rect(0, 0, 80, 60)))) | |
| 113 | + | } | |
| 114 | + | plain, zoomed := walk(scene(0)), walk(scene(0.9)) | |
| 115 | + | // tile fill, tile line, veil, float fill, float content, float line | |
| 116 | + | if len(plain) != 6 || len(zoomed) != 6 { | |
| 117 | + | t.Fatalf("draws: plain %d zoomed %d, want 6", len(plain), len(zoomed)) | |
| 118 | + | } | |
| 119 | + | for i := range plain { | |
| 120 | + | if i == 4 { | |
| 121 | + | continue | |
| 122 | + | } | |
| 123 | + | if plain[i].pc != zoomed[i].pc { | |
| 124 | + | t.Fatalf("draw %d differs with the zoom: %+v vs %+v", i, plain[i].pc, zoomed[i].pc) | |
| 125 | + | } | |
| 126 | + | } | |
| 127 | + | if veil := zoomed[2].pc; veil.rect != [4]int32{0, 0, 80, 60} || veil.color != [4]float32{0, 0, 0, 0.4} { | |
| 128 | + | t.Fatalf("veil = %+v, want the full-output 0.4 dim under the float", veil) | |
| 129 | + | } | |
| 130 | + | if zoomed[1].pc.rect != [4]int32{40, 0, 41, 60} { | |
| 131 | + | t.Fatalf("tile line = %+v, want it painted before the float", zoomed[1].pc.rect) | |
| 132 | + | } | |
| 133 | + | pr, zr := plain[4].pc.rect, zoomed[4].pc.rect | |
| 134 | + | if !(zr[2]-zr[0] < pr[2]-pr[0]) || zr[0] != pr[0] || zr[1] != pr[1] { | |
| 135 | + | t.Fatalf("zoomed content %v, want smaller than %v from the same origin", zr, pr) | |
| 136 | + | } | |
| 137 | + | // A card (Preview) keeps not opening the floats: no tile lines, no veil. | |
| 138 | + | card := scene(0) | |
| 139 | + | card.Windows[1].Preview = 0.9 | |
| 140 | + | if ds := walk(card); len(ds) != 5 || ds[1].pc.color[3] != 0.5 { | |
| 141 | + | t.Fatalf("card draws = %d, want 5 (lines after, no veil): %+v", len(ds), ds) | |
| 142 | + | } | |
| 143 | + | } | |
| @@ -86,3 +86,58 @@ func TestSceneWindowFadeHDR(t *testing.T) { | |||||
| 86 | t.Fatalf("hdr faded white = %v, want rgb %v alpha 0.5", c, want) | 86 | t.Fatalf("hdr faded white = %v, want rgb %v alpha 0.5", c, want) | ||
| 87 | } | 87 | } | ||
| 88 | } | 88 | } | ||
| 89 | + | ||||
| 90 | + | // An appearing or leaving float carries a Zoom, not a Preview: it still | |||
| 91 | + | // opens the floats, so the tile lines and the veil are painted under it, | |||
| 92 | + | // exactly as for the same float unzoomed; only its content draw shrinks. | |||
| 93 | + | func TestSceneZoomedFloatOpensFloats(t *testing.T) { | |||
| 94 | + | r := &Renderer{width: 80, height: 60} | |||
| 95 | + | scene := func(zoom float64) ports.Scene { | |||
| 96 | + | return ports.Scene{ | |||
| 97 | + | Dim: 0.4, Background: "#000000", Border: ports.Border{Width: 1, Active: "#ffffff", Inactive: "#808080"}, | |||
| 98 | + | Windows: []ports.SceneWindow{ | |||
| 99 | + | {ID: 1, Rect: ports.Rect{W: 80, H: 60}}, | |||
| 100 | + | {ID: 2, Floating: true, Rect: ports.Rect{X: 20, Y: 10, W: 40, H: 40}, Inset: ports.SideAll, Fade: 0.5, Zoom: zoom}, | |||
| 101 | + | }, | |||
| 102 | + | Separators: []ports.Separator{ | |||
| 103 | + | {Rect: ports.Rect{X: 40, W: 1, H: 60}}, | |||
| 104 | + | {Rect: ports.Rect{X: 20, Y: 10, W: 1, H: 40}, Window: 2}, | |||
| 105 | + | }, | |||
| 106 | + | } | |||
| 107 | + | } | |||
| 108 | + | contents := map[ports.WindowID]ports.SurfaceContent{ | |||
| 109 | + | 2: {ID: 2, Width: 1, Height: 1, LogicalW: 38, LogicalH: 38, Solid: &ports.SolidColor{R: 1, G: 1, B: 1, A: 1}}, | |||
| 110 | + | } | |||
| 111 | + | walk := func(s ports.Scene) []draw { | |||
| 112 | + | return slices.Clone(r.draws(s, contents, newDamage(&target{}, s, image.Rect(0, 0, 80, 60)))) | |||
| 113 | + | } | |||
| 114 | + | plain, zoomed := walk(scene(0)), walk(scene(0.9)) | |||
| 115 | + | // tile fill, tile line, veil, float fill, float content, float line | |||
| 116 | + | if len(plain) != 6 || len(zoomed) != 6 { | |||
| 117 | + | t.Fatalf("draws: plain %d zoomed %d, want 6", len(plain), len(zoomed)) | |||
| 118 | + | } | |||
| 119 | + | for i := range plain { | |||
| 120 | + | if i == 4 { | |||
| 121 | + | continue | |||
| 122 | + | } | |||
| 123 | + | if plain[i].pc != zoomed[i].pc { | |||
| 124 | + | t.Fatalf("draw %d differs with the zoom: %+v vs %+v", i, plain[i].pc, zoomed[i].pc) | |||
| 125 | + | } | |||
| 126 | + | } | |||
| 127 | + | if veil := zoomed[2].pc; veil.rect != [4]int32{0, 0, 80, 60} || veil.color != [4]float32{0, 0, 0, 0.4} { | |||
| 128 | + | t.Fatalf("veil = %+v, want the full-output 0.4 dim under the float", veil) | |||
| 129 | + | } | |||
| 130 | + | if zoomed[1].pc.rect != [4]int32{40, 0, 41, 60} { | |||
| 131 | + | t.Fatalf("tile line = %+v, want it painted before the float", zoomed[1].pc.rect) | |||
| 132 | + | } | |||
| 133 | + | pr, zr := plain[4].pc.rect, zoomed[4].pc.rect | |||
| 134 | + | if !(zr[2]-zr[0] < pr[2]-pr[0]) || zr[0] != pr[0] || zr[1] != pr[1] { | |||
| 135 | + | t.Fatalf("zoomed content %v, want smaller than %v from the same origin", zr, pr) | |||
| 136 | + | } | |||
| 137 | + | // A card (Preview) keeps not opening the floats: no tile lines, no veil. | |||
| 138 | + | card := scene(0) | |||
| 139 | + | card.Windows[1].Preview = 0.9 | |||
| 140 | + | if ds := walk(card); len(ds) != 5 || ds[1].pc.color[3] != 0.5 { | |||
| 141 | + | t.Fatalf("card draws = %d, want 5 (lines after, no veil): %+v", len(ds), ds) | |||
| 142 | + | } | |||
| 143 | + | } | |||
M internal/ports/scene.go +6 -1
| @@ -430,8 +430,13 @@ type SceneWindow struct { | |||
| 430 | 430 | // needs its kind here and in the push constants (a misc flag). | |
| 431 | 431 | FocusEffect float64 | |
| 432 | 432 | // Preview, above 0, draws the window's surfaces that much smaller in | |
| 433 | - | // Rect (an overview thumbnail): the client keeps its size. | |
| 433 | + | // Rect (an overview thumbnail): the client keeps its size. A preview | |
| 434 | + | // is a card: it never opens the floats. | |
| 434 | 435 | Preview float64 | |
| 436 | + | // Zoom, above 0, is the content scale of a window whose frame is | |
| 437 | + | // animating (appearing, leaving, an overview card in flight): it | |
| 438 | + | // replaces Preview for drawing only; the window keeps its kind. | |
| 439 | + | Zoom float64 | |
| 435 | 440 | // Popups are drawn from their content only: no border, no background. | |
| 436 | 441 | Popup bool | |
| 437 | 442 | // OverLayers popups hang from a layer surface: drawn over the top and | |
| @@ -430,8 +430,13 @@ type SceneWindow struct { | |||||
| 430 | // needs its kind here and in the push constants (a misc flag). | 430 | // needs its kind here and in the push constants (a misc flag). | ||
| 431 | FocusEffect float64 | 431 | FocusEffect float64 | ||
| 432 | // Preview, above 0, draws the window's surfaces that much smaller in | 432 | // Preview, above 0, draws the window's surfaces that much smaller in | ||
| 433 | - | // Rect (an overview thumbnail): the client keeps its size. | 433 | + | // Rect (an overview thumbnail): the client keeps its size. A preview |
| 434 | + | // is a card: it never opens the floats. | |||
| 434 | Preview float64 | 435 | Preview float64 | ||
| 436 | + | // Zoom, above 0, is the content scale of a window whose frame is | |||
| 437 | + | // animating (appearing, leaving, an overview card in flight): it | |||
| 438 | + | // replaces Preview for drawing only; the window keeps its kind. | |||
| 439 | + | Zoom float64 | |||
| 435 | // Popups are drawn from their content only: no border, no background. | 440 | // Popups are drawn from their content only: no border, no background. | ||
| 436 | Popup bool | 441 | Popup bool | ||
| 437 | // OverLayers popups hang from a layer surface: drawn over the top and | 442 | // OverLayers popups hang from a layer surface: drawn over the top and | ||
M internal/ports/scene_same_test.go +1 -0
| @@ -38,6 +38,7 @@ func TestSceneSameAs(t *testing.T) { | |||
| 38 | 38 | for name, edit := range map[string]func(*Scene){ | |
| 39 | 39 | "window rect": func(s *Scene) { s.Windows[0].Rect.X++ }, | |
| 40 | 40 | "window fade": func(s *Scene) { s.Windows[0].Fade = 0.5 }, | |
| 41 | + | "window zoom": func(s *Scene) { s.Windows[0].Zoom = 0.9 }, | |
| 41 | 42 | "window count": func(s *Scene) { s.Windows = nil }, | |
| 42 | 43 | "security": func(s *Scene) { s.Security.Generation++ }, | |
| 43 | 44 | "background": func(s *Scene) { s.Background = "#111111" }, | |
| @@ -38,6 +38,7 @@ func TestSceneSameAs(t *testing.T) { | |||||
| 38 | for name, edit := range map[string]func(*Scene){ | 38 | for name, edit := range map[string]func(*Scene){ | ||
| 39 | "window rect": func(s *Scene) { s.Windows[0].Rect.X++ }, | 39 | "window rect": func(s *Scene) { s.Windows[0].Rect.X++ }, | ||
| 40 | "window fade": func(s *Scene) { s.Windows[0].Fade = 0.5 }, | 40 | "window fade": func(s *Scene) { s.Windows[0].Fade = 0.5 }, | ||
| 41 | + | "window zoom": func(s *Scene) { s.Windows[0].Zoom = 0.9 }, | |||
| 41 | "window count": func(s *Scene) { s.Windows = nil }, | 42 | "window count": func(s *Scene) { s.Windows = nil }, | ||
| 42 | "security": func(s *Scene) { s.Security.Generation++ }, | 43 | "security": func(s *Scene) { s.Security.Generation++ }, | ||
| 43 | "background": func(s *Scene) { s.Background = "#111111" }, | 44 | "background": func(s *Scene) { s.Background = "#111111" }, | ||