Advertisement
pk3456

Window Preview

Feb 15th, 2023 (edited)
485
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #Requires AutoHotkey v2.0
  2.  
  3. #w::WindowPreview("A")
  4.  
  5. /**
  6.  * Creates a live preview of a window using DWM thumbnails.
  7.  * Click and drag the preview window to pan.
  8.  * Use the scroll wheel to zoom in/out.
  9.  * Double click to restore the source window.
  10.  */
  11. class WindowPreview extends Gui {
  12.  
  13.     lastX := 0
  14.     lastY := 0
  15.     zoomedIn := 0
  16.     edgeLimit := 50 ; prevent dragging preview outside of the window edge
  17.  
  18.     __New(source) {
  19.         super.__New("+AlwaysOnTop Resize +ToolWindow -DPIScale MinSize150x150", "Window Preview: " WinGetTitle(source), this)
  20.         this.BackColor := "171717"
  21.         if !source := WinExist(source)
  22.             return
  23.         this.source := source
  24.         this.Thumbnail := DWMThumbnail(this, source)
  25.  
  26.         this.OnEvent("Size", "onSize")
  27.         this.OnEvent("Close", "Destroy")
  28.  
  29.         this.Handlers := {
  30.             DoubleClick: this.OnDoubleClick.bind(this.hwnd),
  31.             LButtonDown: this.OnLButton.bind(this.hwnd),
  32.             LButtonUp: this.OnLButton.bind(this.hwnd),
  33.             Wheel: this.OnMouseWheel.bind(this.hwnd),
  34.             MouseMove: this.OnMouseMove.bind(this.hwnd)
  35.         }
  36.  
  37.         OnMessage(0x0201, this.Handlers.LButtonDown) ; left click to drag the preview
  38.         OnMessage(0x0202, this.Handlers.LButtonUp) ; left click to drag the preview
  39.         OnMessage(0x0203, this.Handlers.DoubleClick) ; double click activates the original window
  40.         OnMessage(0x020A, this.Handlers.Wheel) ; scroll to zoom in/out
  41.  
  42.         this.Thumbnail.querySourceSize(&w, &h)
  43.         this.TL := this.TT := 0
  44.         this.TR := w
  45.         this.TB := h
  46.  
  47.         initialSize := 430
  48.         if w > h
  49.             iW := initialSize, iH := iW * h/w
  50.          else
  51.             iH := initialSize, iW := iH * w/h
  52.         this.Show(Format("w{} h{} NA", iW, iH))
  53.     }
  54.  
  55.     __Delete() {
  56.         OnMessage(0x0201, this.Handlers.LButtonDown, 0)
  57.         OnMessage(0x0202, this.Handlers.LButtonUp, 0)
  58.         OnMessage(0x0203, this.Handlers.DoubleClick, 0)
  59.         OnMessage(0x020A, this.Handlers.Wheel, 0)
  60.     }
  61.  
  62.     OnDoubleClick(wParam, lParam, msg, hwnd) {
  63.         if (this != hwnd)
  64.             return
  65.         this := GuiFromHwnd(this)
  66.         if (this.Hwnd = hwnd)
  67.             WinActivate(this.source)
  68.     }
  69.  
  70.     onSize(MinMax, Width, Height) {
  71.         this.Thumbnail.querySourceSize(&sW, &sH)
  72.  
  73.         if !this.zoomedIn {
  74.             if sW * Height > Width * sH {
  75.                 ; h0 = height, h1 = vertical offset
  76.                 h0 := sH * Width / sW, h1 := (Height - h0) / 2
  77.                 this.TL := 0, this.TT := h1, this.TR := Width, this.TB := h0 + h1
  78.             } else {
  79.                 ; w0 = width, w1 = horizontal offset
  80.                 w0 := sW * Height / sH, w1 := (Width - w0) / 2
  81.                 this.TL := w1, this.TT := 0, this.TR := w0 + w1, this.TB := Height
  82.             }
  83.             this.Thumbnail.SetThumbRegion(this.TL, this.TT, this.TR, this.TB)
  84.         }
  85.     }
  86.  
  87.     OnLButton(wParam, lParam, msg, hwnd) {
  88.         if (this != hwnd)
  89.             return
  90.         this := GuiFromHwnd(this)
  91.  
  92.         if msg = 0x201 {
  93.             this.lastx := lParam & 0xFFFF
  94.             this.lasty := (lParam >> 16) & 0xFFFF
  95.             OnMessage(0x200, this.Handlers.MouseMove)
  96.             DllCall("SetCapture", "Ptr", this.hwnd) ; allow gui to receive WM_MOUSEMOVE even if cursor is outside the gui
  97.         }
  98.         else {
  99.             OnMessage(0x200, this.Handlers.MouseMove, 0)
  100.             DllCall("ReleaseCapture")
  101.         }
  102.     }
  103.  
  104.     OnMouseMove(wParam, lParam, msg, hwnd) {
  105.         if (this != hwnd)
  106.             return
  107.         this := GuiFromHwnd(this)
  108.  
  109.         x := (lParam & 0xFFFF) << 48 >> 48
  110.         y := lParam << 32 >> 48
  111.         this.GetClientPos(,,&gW, &gH)
  112.  
  113.         ; mouse movement relative to last position
  114.         dx := x - this.lastX
  115.         dy := y - this.lastY
  116.  
  117.         if !(this.TL+dx > gW-this.edgeLimit) && !(this.TR+dx < this.edgeLimit)
  118.             this.TL += dx, this.TR += dx
  119.  
  120.         if !(this.TT+dy > gH-this.edgeLimit) && !(this.TB+dy < this.edgeLimit)
  121.             this.TT += dy, this.TB += dy
  122.  
  123.         this.zoomedIn := !(this.TL > 0 && this.TT > 0 && this.TR < gW && this.TB < gH)
  124.  
  125.         ; TL, TT, TR, RB : Left, Top, Right, Bottom of thumbnail
  126.         this.Thumbnail.SetThumbRegion(this.TL, this.TT, this.TR, this.TB)
  127.         this.lastX := x, this.lastY := y
  128.     }
  129.  
  130.     ; zoom in/out on scroll
  131.     OnMouseWheel(wParam, lParam, msg, hwnd) {
  132.         if (this != hwnd)
  133.             return
  134.         this := GuiFromHwnd(this)
  135.  
  136.         delta := ((wParam << 32 >> 48) // 120)
  137.         this.GetClientPos(&gX, &gY, &gW, &gH)
  138.  
  139.         ; convert lParam coordinates from screen to client coordinates
  140.         mx := (lParam << 48 >> 48) - gX
  141.         my := (lParam << 32 >> 48) - gY
  142.  
  143.         this.Thumbnail.querySourceSize(&Sw, &Sh)
  144.  
  145.         zoomFactor := 1 + delta * 0.05
  146.         aspectRatio := Sw / Sh
  147.  
  148.         width := (this.TR - this.TL) * zoomFactor
  149.         height := width / aspectRatio
  150.  
  151.         TL := mX - (mX - this.TL) * zoomFactor
  152.         TT := mY - (mY - this.TT) * zoomFactor
  153.         TR := TL + width
  154.         TB := TT + height
  155.  
  156.         zoomLevel := (TR-TL)/sW
  157.         minZoomLevel := 0.2, maxZoomLevel := 5
  158.         if zoomLevel < minZoomLevel || zoomLevel > maxZoomLevel
  159.             return
  160.  
  161.         this.TL := mX - (mX - this.TL) * zoomFactor
  162.         this.TT := mY - (mY - this.TT) * zoomFactor
  163.         this.TR := this.TL + width
  164.         this.TB := this.TT + height
  165.  
  166.         width := this.TR - this.TL
  167.  
  168.         if this.TL > gW-this.edgeLimit {
  169.             this.TL := gW-this.edgeLimit, this.TR := this.TL + width
  170.         } else if this.TR < this.edgeLimit {
  171.             this.TR := this.edgeLimit, this.TL := this.TR - width
  172.         }
  173.  
  174.         height := this.TB - this.TT
  175.         if this.TT > gH-this.edgeLimit {
  176.             this.TT := gH-this.edgeLimit, this.TB := this.TT + height
  177.         } else if this.TB < this.edgeLimit {
  178.             this.TB := this.edgeLimit, this.TT := this.TB - height
  179.         }
  180.  
  181.         this.zoomedIn := !(this.TL > 0 && this.TT > 0 && this.TR < gW && this.TB < gH)
  182.         this.Thumbnail.SetThumbRegion(this.TL, this.TT, this.TR, this.TB)
  183.     }
  184. }
  185.  
  186. ; https://github.com/thqby/ahk2_lib/blob/master/DWMThumbnail.ahk
  187. class DWMThumbnail {
  188.     __New(gui, source, clientOnly:=true) {
  189.         hGui := gui.HasProp("hwnd") ? gui.hwnd : gui
  190.         source := WinExist(source)
  191.         if hr := DllCall("dwmapi\DwmRegisterThumbnail", "ptr", hGui, "ptr", source, "ptr*", &thumbnail := 0)
  192.             throw OSError(hr)
  193.         this.ptr := thumbnail
  194.         this.setClientOnly(clientOnly)
  195.     }
  196.     querySourceSize(&width, &height) {
  197.         if hr := DllCall("dwmapi\DwmQueryThumbnailSourceSize", "ptr",this, "ptr", size := Buffer(8))
  198.             throw OSError(hr)
  199.         width := NumGet(size, "int")
  200.         height := NumGet(size, 4, "int")
  201.     }
  202.     SetSourceRegion(coords*) {
  203.         this.updateProperties({s:coords})
  204.     }
  205.     SetThumbRegion(coords*) {
  206.         this.updateProperties({d:coords})
  207.     }
  208.     updateProperties(properties) {
  209.         buf := Buffer(45, 0), flags := 0
  210.         for k, v in properties.OwnProps() {
  211.             switch k, false {
  212.             case "rcDestination", "dest", "d":
  213.                 ; DWM_TNP_RECTDESTINATION = 1
  214.                 flags |= 1, NumPut("int", v[1], "int", v[2], "int", v[3], "int", v[4], buf, 4)
  215.             case "rcSource", "src", "s":
  216.                 if v ; DWM_TNP_RECTSOURCE = 2
  217.                     flags |= 2, NumPut("int", v[1], "int", v[2], "int", v[3], "int", v[4], buf, 20)
  218.             case "opacity", "o":
  219.                 ; DWM_TNP_OPACITY = 4
  220.                 flags |= 4, NumPut("uchar", v, buf, 36)
  221.             case "fVisible", "vis", "v":
  222.                 ; DWM_TNP_VISIBLE = 8
  223.                 flags |= 8, NumPut("int", v, buf, 37)
  224.             case "fSourceClientAreaOnly", "client", "c":
  225.                 ; DWM_TNP_SOURCECLIENTAREAONLY = 16
  226.                 flags |= 16, NumPut("int", v, buf, 41)
  227.             }
  228.         }
  229.         NumPut("uint", flags, buf)
  230.         if hr := DllCall("dwmapi\DwmUpdateThumbnailProperties", "ptr", this, "ptr", buf)
  231.             throw OSError(hr)
  232.     }
  233.     setVisible(vis := true) => this.updateProperties({v:vis})
  234.     setClientOnly(client := true) => this.updateProperties({c:client})
  235.     setOpacity(opacity) => this.updateProperties({o: opacity})
  236.     __Delete() {
  237.         DllCall("dwmapi\DwmUnregisterThumbnail", "ptr", this)
  238.     }
  239. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement