mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-18 17:24:01 -04:00
feat(screenshot): add live preview panel for scroll capture (#3269)
* feat(screenshot): add live preview panel for scroll capture - Port scrolling live preview from dms-quick-capture backend to native screenshot. - Calculate dynamic preview panel geometry placed beside the capture hole on the side with most available space. - Render downscaled live stitched canvas with dark translucent background and white border in drawScrollOverlay. - Include preview panel in Wayland input regions and allow completing capture by clicking the preview panel. - Update cursor shape to pointer hand when hovering over preview or control buttons. - Add unit tests for layout, hit testing, and canvas rendering. * fix(screenshot): bound scroll preview aspect ratio to recent canvas tail - Derive preview aspect ratio from a bounded tail window of the canvas (maxPreviewRows = frameW * maxHeight / minImageW). - Prevent preview panel width from collapsing below minImageW and disappearing when stitched rows exceed ~4.5x width. - Render the recent window rows in drawScrollPreview to maintain a stable panel size and display live incoming content. - Add unit test for long scrolling capture preview bounds.
This commit is contained in:
@@ -660,7 +660,14 @@ func (r *RegionSelector) setNativeCursor(serial uint32) {
|
||||
return
|
||||
}
|
||||
shape := uint32(wp_cursor_shape.WpCursorShapeDeviceV1ShapeCrosshair)
|
||||
if r.resizingHandle != handleNone {
|
||||
if r.phase == phaseScroll {
|
||||
switch r.scrollBarHit(r.pointerX, r.pointerY) {
|
||||
case "done", "cancel", "preview":
|
||||
shape = uint32(wp_cursor_shape.WpCursorShapeDeviceV1ShapePointer)
|
||||
default:
|
||||
shape = uint32(wp_cursor_shape.WpCursorShapeDeviceV1ShapeDefault)
|
||||
}
|
||||
} else if r.resizingHandle != handleNone {
|
||||
shape = cursorShapeForHandle(r.resizingHandle)
|
||||
} else if r.movingSelection && r.selection.dragging {
|
||||
shape = uint32(wp_cursor_shape.WpCursorShapeDeviceV1ShapeGrabbing)
|
||||
|
||||
@@ -58,6 +58,11 @@ func (r *RegionSelector) setupPointerHandlers() {
|
||||
r.pointerX = e.SurfaceX
|
||||
r.pointerY = e.SurfaceY
|
||||
|
||||
if r.phase == phaseScroll {
|
||||
r.refreshCursor()
|
||||
return
|
||||
}
|
||||
|
||||
if !r.selection.dragging {
|
||||
if r.ctrlHeld && r.selection.hasSelection {
|
||||
r.refreshCursor()
|
||||
@@ -78,7 +83,7 @@ func (r *RegionSelector) setupPointerHandlers() {
|
||||
return
|
||||
}
|
||||
switch r.scrollBarHit(r.pointerX, r.pointerY) {
|
||||
case "done":
|
||||
case "done", "preview":
|
||||
r.finishScroll()
|
||||
case "cancel":
|
||||
r.cancelled = true
|
||||
|
||||
@@ -496,6 +496,98 @@ func (r *RegionSelector) drawScrollOverlay(os *OutputSurface, renderBuf *ShmBuff
|
||||
|
||||
r.drawBorder(data, stride, w, h, holeX-1, holeY-1, holeW+2, holeH+2, os.screenFormat)
|
||||
r.drawScrollBar(data, stride, w, h, os.screenFormat)
|
||||
r.drawScrollPreview(data, stride, w, h, os)
|
||||
}
|
||||
|
||||
func (r *RegionSelector) drawScrollPreview(data []byte, stride, bufW, bufH int, os *OutputSurface) {
|
||||
s := r.scroll
|
||||
if s == nil || s.st == nil || s.st.rows() == 0 || !s.hasPreview {
|
||||
return
|
||||
}
|
||||
|
||||
canvas := s.st.canvas
|
||||
sourceW := s.frameW
|
||||
sourceRows := s.st.rows()
|
||||
if sourceW <= 0 || sourceRows <= 0 || len(canvas) < sourceW*sourceRows*4 {
|
||||
return
|
||||
}
|
||||
|
||||
scale := 1.0
|
||||
if os.logicalW > 0 {
|
||||
scale = float64(bufW) / float64(os.logicalW)
|
||||
}
|
||||
if scale <= 0 {
|
||||
scale = 1.0
|
||||
}
|
||||
padding := int(float64(scrollPreviewPaddingLogical) * scale)
|
||||
|
||||
format := os.screenFormat
|
||||
// 1. Draw translucent dark panel background
|
||||
r.fillRect(data, stride, bufW, bufH, s.previewX, s.previewY, s.previewW, s.previewH, 16, 16, 16, 230, format)
|
||||
|
||||
// 2. Draw downscaled stitched canvas
|
||||
imageX := s.previewX + padding
|
||||
imageY := s.previewY + padding
|
||||
imageW := s.previewW - padding*2
|
||||
imageH := s.previewH - padding*2
|
||||
if imageW <= 0 || imageH <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
needSwap := formatIsBGR(uint32(s.format)) != formatIsBGR(format)
|
||||
|
||||
startRow := s.previewStartRow
|
||||
previewRows := s.previewRows
|
||||
if previewRows <= 0 || startRow < 0 || startRow+previewRows > sourceRows {
|
||||
startRow = 0
|
||||
previewRows = sourceRows
|
||||
}
|
||||
|
||||
for y := range imageH {
|
||||
srcY := startRow + y*previewRows/imageH
|
||||
if srcY >= sourceRows {
|
||||
srcY = sourceRows - 1
|
||||
}
|
||||
dstY := imageY + y
|
||||
if dstY < 0 || dstY >= bufH {
|
||||
continue
|
||||
}
|
||||
dstRowOff := dstY * stride
|
||||
srcRowOff := srcY * sourceW * 4
|
||||
|
||||
for x := range imageW {
|
||||
srcX := x * sourceW / imageW
|
||||
dstX := imageX + x
|
||||
if dstX < 0 || dstX >= bufW {
|
||||
continue
|
||||
}
|
||||
srcIdx := srcRowOff + srcX*4
|
||||
dstIdx := dstRowOff + dstX*4
|
||||
if srcIdx+3 >= len(canvas) || dstIdx+3 >= len(data) {
|
||||
continue
|
||||
}
|
||||
|
||||
if needSwap {
|
||||
data[dstIdx+0] = canvas[srcIdx+2]
|
||||
data[dstIdx+1] = canvas[srcIdx+1]
|
||||
data[dstIdx+2] = canvas[srcIdx+0]
|
||||
} else {
|
||||
data[dstIdx+0] = canvas[srcIdx+0]
|
||||
data[dstIdx+1] = canvas[srcIdx+1]
|
||||
data[dstIdx+2] = canvas[srcIdx+2]
|
||||
}
|
||||
data[dstIdx+3] = 255
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Draw border around preview panel
|
||||
borderW := max(int(scale+0.5), 1)
|
||||
for i := range borderW {
|
||||
r.drawHLine(data, stride, bufW, bufH, s.previewX-i, s.previewY-i, s.previewW+2*i, format)
|
||||
r.drawHLine(data, stride, bufW, bufH, s.previewX-i, s.previewY+s.previewH+i-1, s.previewW+2*i, format)
|
||||
r.drawVLine(data, stride, bufW, bufH, s.previewX-i, s.previewY-i, s.previewH+2*i, format)
|
||||
r.drawVLine(data, stride, bufW, bufH, s.previewX+s.previewW+i-1, s.previewY-i, s.previewH+2*i, format)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RegionSelector) drawScrollBar(data []byte, stride, bufW, bufH int, format uint32) {
|
||||
|
||||
@@ -25,6 +25,12 @@ const (
|
||||
const (
|
||||
scrollMaxFailures = 5
|
||||
scrollSeamTicks = 4
|
||||
|
||||
scrollPreviewMarginLogical = 16
|
||||
scrollPreviewPaddingLogical = 6
|
||||
scrollPreviewMinImageWLogical = 80
|
||||
scrollPreviewMaxImageWLogical = 240
|
||||
scrollPreviewMaxImageHLogical = 360
|
||||
)
|
||||
|
||||
type scrollSession struct {
|
||||
@@ -61,6 +67,11 @@ type scrollSession struct {
|
||||
cancelW int
|
||||
btnH int
|
||||
|
||||
// preview panel geometry and window in overlay buffer pixels
|
||||
previewX, previewY, previewW, previewH int
|
||||
previewStartRow, previewRows int
|
||||
hasPreview bool
|
||||
|
||||
sigCh chan os.Signal
|
||||
keysBound bool
|
||||
|
||||
@@ -195,9 +206,12 @@ func (r *RegionSelector) layoutScrollBar(os *OutputSurface) {
|
||||
|
||||
borderX1, borderY1 := s.holeX-3, s.holeY-3
|
||||
borderX2, borderY2 := s.holeX+s.holeW+3, s.holeY+s.holeH+3
|
||||
overlaps := s.barX < borderX2 && s.barX+s.barW > borderX1 &&
|
||||
overlapsHole := s.barX < borderX2 && s.barX+s.barW > borderX1 &&
|
||||
s.barY < borderY2 && s.barY+s.barH > borderY1
|
||||
if overlaps {
|
||||
overlapsPreview := s.hasPreview &&
|
||||
s.barX < s.previewX+s.previewW && s.barX+s.barW > s.previewX &&
|
||||
s.barY < s.previewY+s.previewH && s.barY+s.barH > s.previewY
|
||||
if overlapsHole || overlapsPreview {
|
||||
s.barY = 24
|
||||
}
|
||||
|
||||
@@ -207,7 +221,108 @@ func (r *RegionSelector) layoutScrollBar(os *OutputSurface) {
|
||||
s.cancelY = s.doneY
|
||||
}
|
||||
|
||||
func (r *RegionSelector) scrollPreviewPanel(os *OutputSurface) (x, y, w, h int, startRow, previewRows int, ok bool) {
|
||||
s := r.scroll
|
||||
if s == nil || s.st == nil || s.frameW <= 0 || s.st.rows() <= 0 || os == nil || os.screenBuf == nil {
|
||||
return 0, 0, 0, 0, 0, 0, false
|
||||
}
|
||||
|
||||
bufW, bufH := os.screenBuf.Width, os.screenBuf.Height
|
||||
scale := 1.0
|
||||
if os.logicalW > 0 {
|
||||
scale = float64(bufW) / float64(os.logicalW)
|
||||
}
|
||||
if scale <= 0 {
|
||||
scale = 1.0
|
||||
}
|
||||
|
||||
margin := int(float64(scrollPreviewMarginLogical) * scale)
|
||||
padding := int(float64(scrollPreviewPaddingLogical) * scale)
|
||||
minImageW := int(float64(scrollPreviewMinImageWLogical) * scale)
|
||||
maxImageW := int(float64(scrollPreviewMaxImageWLogical) * scale)
|
||||
maxImageH := int(float64(scrollPreviewMaxImageHLogical) * scale)
|
||||
|
||||
selX := s.holeX
|
||||
selY := s.holeY
|
||||
selW := s.holeW
|
||||
selH := s.holeH
|
||||
|
||||
leftGap := selX
|
||||
rightGap := bufW - (selX + selW)
|
||||
if leftGap < 0 {
|
||||
leftGap = 0
|
||||
}
|
||||
if rightGap < 0 {
|
||||
rightGap = 0
|
||||
}
|
||||
|
||||
onLeft := leftGap >= rightGap
|
||||
sideGap := rightGap
|
||||
if onLeft {
|
||||
sideGap = leftGap
|
||||
}
|
||||
|
||||
maxHeight := min(maxImageH, bufH-margin*2)
|
||||
availableWidth := sideGap - (margin*2 + padding*2)
|
||||
if maxHeight <= 0 || availableWidth < minImageW {
|
||||
return 0, 0, 0, 0, 0, 0, false
|
||||
}
|
||||
|
||||
totalRows := s.st.rows()
|
||||
maxPreviewRows := int(float64(s.frameW) * float64(maxHeight) / float64(minImageW))
|
||||
startRow = 0
|
||||
previewRows = totalRows
|
||||
if maxPreviewRows > 0 && totalRows > maxPreviewRows {
|
||||
startRow = totalRows - maxPreviewRows
|
||||
previewRows = maxPreviewRows
|
||||
}
|
||||
|
||||
aspect := float64(s.frameW) / float64(previewRows)
|
||||
imageWidth := int(float64(maxHeight)*aspect + 0.5)
|
||||
imageWidth = min(imageWidth, maxImageW)
|
||||
imageWidth = min(imageWidth, availableWidth)
|
||||
if imageWidth < minImageW {
|
||||
return 0, 0, 0, 0, 0, 0, false
|
||||
}
|
||||
|
||||
imageHeight := max(int(float64(imageWidth)/aspect+0.5), 1)
|
||||
panelWidth := imageWidth + padding*2
|
||||
panelHeight := imageHeight + padding*2
|
||||
|
||||
var panelX int
|
||||
if onLeft {
|
||||
panelX = selX - margin - panelWidth
|
||||
} else {
|
||||
panelX = selX + selW + margin
|
||||
}
|
||||
|
||||
panelY := selY + (selH-panelHeight)/2
|
||||
panelY = clamp(panelY, margin, bufH-margin-panelHeight)
|
||||
|
||||
return panelX, panelY, panelWidth, panelHeight, startRow, previewRows, true
|
||||
}
|
||||
|
||||
func (r *RegionSelector) updateScrollPreviewLayout(os *OutputSurface) {
|
||||
s := r.scroll
|
||||
if s == nil || os == nil {
|
||||
return
|
||||
}
|
||||
px, py, pw, ph, startRow, previewRows, ok := r.scrollPreviewPanel(os)
|
||||
changed := s.hasPreview != ok || s.previewX != px || s.previewY != py || s.previewW != pw || s.previewH != ph
|
||||
s.previewX, s.previewY, s.previewW, s.previewH = px, py, pw, ph
|
||||
s.previewStartRow, s.previewRows = startRow, previewRows
|
||||
s.hasPreview = ok
|
||||
|
||||
if changed {
|
||||
r.layoutScrollBar(os)
|
||||
r.setInputPassthrough(os, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RegionSelector) setInputPassthrough(os *OutputSurface, withBar bool) {
|
||||
if r.compositor == nil || os == nil || os.wlSurface == nil {
|
||||
return
|
||||
}
|
||||
reg, err := r.compositor.CreateRegion()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -218,6 +333,10 @@ func (r *RegionSelector) setInputPassthrough(os *OutputSurface, withBar bool) {
|
||||
scaleY := float64(os.logicalH) / float64(os.screenBuf.Height)
|
||||
_ = reg.Add(int32(float64(s.barX)*scaleX), int32(float64(s.barY)*scaleY),
|
||||
int32(float64(s.barW)*scaleX)+1, int32(float64(s.barH)*scaleY)+1)
|
||||
if s.hasPreview {
|
||||
_ = reg.Add(int32(float64(s.previewX)*scaleX), int32(float64(s.previewY)*scaleY),
|
||||
int32(float64(s.previewW)*scaleX)+1, int32(float64(s.previewH)*scaleY)+1)
|
||||
}
|
||||
}
|
||||
_ = os.wlSurface.SetInputRegion(reg)
|
||||
_ = reg.Destroy()
|
||||
@@ -304,6 +423,8 @@ func (r *RegionSelector) scrollBarHit(x, y float64) string {
|
||||
return "done"
|
||||
case bx >= s.cancelX && bx < s.cancelX+s.cancelW && by >= s.cancelY && by < s.cancelY+s.btnH:
|
||||
return "cancel"
|
||||
case s.hasPreview && bx >= s.previewX && bx < s.previewX+s.previewW && by >= s.previewY && by < s.previewY+s.previewH:
|
||||
return "preview"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -468,6 +589,7 @@ func (r *RegionSelector) handleScrollFrame() {
|
||||
}
|
||||
s.kept++
|
||||
if r.selection.surface != nil {
|
||||
r.updateScrollPreviewLayout(r.selection.surface)
|
||||
r.redrawSurface(r.selection.surface)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
package screenshot
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func createMockScrollSurface(bufW, bufH, logicalW, logicalH int) *OutputSurface {
|
||||
return &OutputSurface{
|
||||
logicalW: logicalW,
|
||||
logicalH: logicalH,
|
||||
output: &WaylandOutput{
|
||||
fractionalScale: float64(bufW) / float64(logicalW),
|
||||
},
|
||||
screenBuf: &ShmBuffer{
|
||||
Width: bufW,
|
||||
Height: bufH,
|
||||
Stride: bufW * 4,
|
||||
},
|
||||
screenFormat: uint32(FormatXRGB8888),
|
||||
}
|
||||
}
|
||||
|
||||
func TestScrollPreviewPanelLayoutRight(t *testing.T) {
|
||||
os := createMockScrollSurface(1920, 1080, 1920, 1080)
|
||||
st := newStitcher(600 * 4)
|
||||
st.canvas = make([]byte, 600*4*1200)
|
||||
st.cols = make([]rowCols, 1200)
|
||||
|
||||
r := &RegionSelector{
|
||||
scroll: &scrollSession{
|
||||
holeX: 100,
|
||||
holeY: 100,
|
||||
holeW: 600,
|
||||
holeH: 800,
|
||||
frameW: 600,
|
||||
st: st,
|
||||
},
|
||||
}
|
||||
|
||||
x, y, w, h, startRow, previewRows, ok := r.scrollPreviewPanel(os)
|
||||
if !ok {
|
||||
t.Fatalf("expected scrollPreviewPanel to succeed, got ok=false")
|
||||
}
|
||||
if startRow != 0 || previewRows != 1200 {
|
||||
t.Errorf("expected full canvas window (0, 1200), got (%d, %d)", startRow, previewRows)
|
||||
}
|
||||
|
||||
// Selection is at x=100..700. Right gap is 1920 - 700 = 1220; left gap is 100.
|
||||
// Panel must be placed to the right of the hole.
|
||||
if x <= r.scroll.holeX+r.scroll.holeW {
|
||||
t.Errorf("panel x=%d should be to the right of hole end %d", x, r.scroll.holeX+r.scroll.holeW)
|
||||
}
|
||||
if x+w > 1920 {
|
||||
t.Errorf("panel right edge %d exceeds screen width 1920", x+w)
|
||||
}
|
||||
if y < 0 || y+h > 1080 {
|
||||
t.Errorf("panel y=%d h=%d out of screen bounds [0, 1080]", y, h)
|
||||
}
|
||||
if y < 0 || y+h > 1080 || w <= 0 || h <= 0 {
|
||||
t.Errorf("invalid dimensions w=%d h=%d", w, h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScrollPreviewPanelLayoutLeft(t *testing.T) {
|
||||
os := createMockScrollSurface(1920, 1080, 1920, 1080)
|
||||
st := newStitcher(600 * 4)
|
||||
st.canvas = make([]byte, 600*4*1200)
|
||||
st.cols = make([]rowCols, 1200)
|
||||
|
||||
r := &RegionSelector{
|
||||
scroll: &scrollSession{
|
||||
holeX: 1220,
|
||||
holeY: 100,
|
||||
holeW: 600,
|
||||
holeH: 800,
|
||||
frameW: 600,
|
||||
st: st,
|
||||
},
|
||||
}
|
||||
|
||||
x, y, w, h, _, _, ok := r.scrollPreviewPanel(os)
|
||||
if !ok {
|
||||
t.Fatalf("expected scrollPreviewPanel to succeed, got ok=false")
|
||||
}
|
||||
|
||||
// Selection is at x=1220..1820. Left gap is 1220; right gap is 100.
|
||||
// Panel must be placed to the left of the hole.
|
||||
if x+w >= r.scroll.holeX {
|
||||
t.Errorf("panel right edge %d should be to the left of hole start %d", x+w, r.scroll.holeX)
|
||||
}
|
||||
if x < 0 {
|
||||
t.Errorf("panel left edge %d is negative", x)
|
||||
}
|
||||
if y < 0 || y+h > 1080 {
|
||||
t.Errorf("panel y=%d h=%d out of screen bounds [0, 1080]", y, h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScrollPreviewPanelInsufficientSpace(t *testing.T) {
|
||||
os := createMockScrollSurface(1920, 1080, 1920, 1080)
|
||||
st := newStitcher(1880 * 4)
|
||||
st.canvas = make([]byte, 1880*4*2000)
|
||||
st.cols = make([]rowCols, 2000)
|
||||
|
||||
r := &RegionSelector{
|
||||
scroll: &scrollSession{
|
||||
holeX: 20,
|
||||
holeY: 20,
|
||||
holeW: 1880,
|
||||
holeH: 1040,
|
||||
frameW: 1880,
|
||||
st: st,
|
||||
},
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, ok := r.scrollPreviewPanel(os)
|
||||
if ok {
|
||||
t.Fatalf("expected scrollPreviewPanel to return false when side gaps are too small")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScrollPreviewPanelBoundedTailLongCapture(t *testing.T) {
|
||||
os := createMockScrollSurface(1920, 1080, 1920, 1080)
|
||||
st := newStitcher(600 * 4)
|
||||
totalRows := 10000
|
||||
st.canvas = make([]byte, 600*4*totalRows)
|
||||
st.cols = make([]rowCols, totalRows)
|
||||
|
||||
r := &RegionSelector{
|
||||
scroll: &scrollSession{
|
||||
holeX: 100,
|
||||
holeY: 100,
|
||||
holeW: 600,
|
||||
holeH: 800,
|
||||
frameW: 600,
|
||||
st: st,
|
||||
},
|
||||
}
|
||||
|
||||
x, y, w, h, startRow, previewRows, ok := r.scrollPreviewPanel(os)
|
||||
if !ok {
|
||||
t.Fatalf("expected scrollPreviewPanel to succeed on long capture, got ok=false")
|
||||
}
|
||||
|
||||
if startRow <= 0 {
|
||||
t.Errorf("expected startRow > 0 for 10000-row capture, got %d", startRow)
|
||||
}
|
||||
if startRow+previewRows != totalRows {
|
||||
t.Errorf("expected startRow (%d) + previewRows (%d) == %d", startRow, previewRows, totalRows)
|
||||
}
|
||||
if y < 0 || y+h > 1080 || w <= 0 || h <= 0 {
|
||||
t.Errorf("invalid dimensions w=%d, h=%d", w, h)
|
||||
}
|
||||
if x <= r.scroll.holeX+r.scroll.holeW {
|
||||
t.Errorf("panel x=%d should be to the right of hole end", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScrollBarHitPreview(t *testing.T) {
|
||||
os := createMockScrollSurface(1920, 1080, 1920, 1080)
|
||||
r := &RegionSelector{
|
||||
selection: SelectionState{
|
||||
surface: os,
|
||||
},
|
||||
scroll: &scrollSession{
|
||||
doneX: 100,
|
||||
doneY: 1000,
|
||||
doneW: 60,
|
||||
btnH: 24,
|
||||
cancelX: 180,
|
||||
cancelY: 1000,
|
||||
cancelW: 70,
|
||||
previewX: 800,
|
||||
previewY: 200,
|
||||
previewW: 200,
|
||||
previewH: 300,
|
||||
hasPreview: true,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
x, y float64
|
||||
want string
|
||||
}{
|
||||
{"done hit", 120, 1010, "done"},
|
||||
{"cancel hit", 200, 1010, "cancel"},
|
||||
{"preview hit", 900, 350, "preview"},
|
||||
{"miss", 500, 500, ""},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := r.scrollBarHit(tc.x, tc.y)
|
||||
if got != tc.want {
|
||||
t.Errorf("scrollBarHit(%.1f, %.1f) = %q, want %q", tc.x, tc.y, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrawScrollPreviewRendering(t *testing.T) {
|
||||
bufW, bufH := 1920, 1080
|
||||
renderBuf, err := CreateShmBuffer(bufW, bufH, bufW*4)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateShmBuffer: %v", err)
|
||||
}
|
||||
defer renderBuf.Close()
|
||||
|
||||
os := &OutputSurface{
|
||||
logicalW: bufW,
|
||||
logicalH: bufH,
|
||||
screenBuf: renderBuf,
|
||||
screenFormat: uint32(FormatXRGB8888),
|
||||
output: &WaylandOutput{fractionalScale: 1.0},
|
||||
}
|
||||
|
||||
sourceW, sourceRows := 400, 800
|
||||
st := newStitcher(sourceW * 4)
|
||||
st.canvas = make([]byte, sourceW*4*sourceRows)
|
||||
st.cols = make([]rowCols, sourceRows)
|
||||
for y := range sourceRows {
|
||||
for x := range sourceW {
|
||||
idx := (y*sourceW + x) * 4
|
||||
st.canvas[idx+0] = 50 // B
|
||||
st.canvas[idx+1] = 150 // G
|
||||
st.canvas[idx+2] = 200 // R
|
||||
st.canvas[idx+3] = 255 // A
|
||||
}
|
||||
}
|
||||
|
||||
r := &RegionSelector{
|
||||
selection: SelectionState{
|
||||
surface: os,
|
||||
},
|
||||
scroll: &scrollSession{
|
||||
holeX: 100,
|
||||
holeY: 100,
|
||||
holeW: 500,
|
||||
holeH: 600,
|
||||
frameW: sourceW,
|
||||
format: PixelFormat(FormatXRGB8888),
|
||||
st: st,
|
||||
},
|
||||
}
|
||||
|
||||
r.updateScrollPreviewLayout(os)
|
||||
if !r.scroll.hasPreview {
|
||||
t.Fatalf("expected hasPreview=true")
|
||||
}
|
||||
|
||||
data := renderBuf.Data()
|
||||
r.drawScrollPreview(data, renderBuf.Stride, bufW, bufH, os)
|
||||
|
||||
// Sample center of preview image
|
||||
padding := int(float64(scrollPreviewPaddingLogical))
|
||||
centerX := r.scroll.previewX + padding + (r.scroll.previewW-padding*2)/2
|
||||
centerY := r.scroll.previewY + padding + (r.scroll.previewH-padding*2)/2
|
||||
|
||||
pixelIdx := centerY*renderBuf.Stride + centerX*4
|
||||
b := data[pixelIdx+0]
|
||||
g := data[pixelIdx+1]
|
||||
red := data[pixelIdx+2]
|
||||
a := data[pixelIdx+3]
|
||||
|
||||
if red != 200 || g != 150 || b != 50 || a != 255 {
|
||||
t.Errorf("center pixel = (%d, %d, %d, %d), want (200, 150, 50, 255)", red, g, b, a)
|
||||
}
|
||||
|
||||
// Sample top-left border of preview panel
|
||||
borderIdx := r.scroll.previewY*renderBuf.Stride + r.scroll.previewX*4
|
||||
if data[borderIdx+0] != 255 || data[borderIdx+1] != 255 || data[borderIdx+2] != 255 {
|
||||
t.Errorf("border pixel = (%d, %d, %d), want white (255, 255, 255)",
|
||||
data[borderIdx+2], data[borderIdx+1], data[borderIdx+0])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user