'
' gfxResize.bas
' contains functions to aspect ratio scale a window, which works windowed and in fullscreen, 
' also a function to colorize the window border.
'
' this module is part of the function collection by RetroCoders-Archive, licensed
' under the GNU-GPLv3
'
' function listing:
'   sub gfx.PreResize(iUndo as integer=0)   
'   sub gfx.Resize(iWid as integer=0,iHei as integer=0,iCenter as integer=1,iResizable as integer=0)
'   sub gfx.BorderColor( iR as ulong , iG as ulong , iB as ulong )
'

#include once "windows.bi"
#include once "crt.bi"
#include once "fbgfx.bi"

#undef HWND_TOPMOST
#undef HWND_NOTOPMOST
#define HWND_TOPMOST cast(handle, -1)
#define HWND_NOTOPMOST cast(handle, -2)
#define GfxResize

extern __fb_gl(5) alias "__fb_gl" as any ptr 
extern GfxWin32(290) alias "fb_win32" as any ptr

namespace gfx
  dim shared as long lOrgFbProc,lOldStyle,lScreenFlags
  dim shared as integer g_iScrWid,g_iScrHei,g_lFirstSize,g_iAspect,g_iOffLeft,g_iOffTop
  dim shared as integer g_iCliWid,g_iCliHei
  dim shared as byte g_Temp,g_Fullscreen
  dim shared as string g_sGfxDriver
  dim shared as COLORREF g_BorderColor
  dim shared as any ptr g_pRevertGdi1, g_pRevertGdi2
  
  #ifndef NoGlResize
    dim shared GlViewPortOrg as sub (lt as long,tp as long,rt as long,bt as long)
    sub GlViewPortDetour(lt as long,tp as long,rt as long,bt as long)  
      if g_iCliWid then lt = 0 : tp = 0 : rt = g_iCliWid : bt = g_iCliHei    
      GlViewPortOrg(lt,tp,rt,bt)
    end sub
  #endif
  
  #ifndef NoGdiResize
    function AutoPatch( pSource as any ptr , pBase as any ptr , pTarget as any ptr ) as any ptr    
      dim as any ptr ptr pFound = pBase
      var pSource2 = *cptr(any ptr ptr,pSource+2)  
      if pTarget then 'do the hook
        var uBase = cuint(pBase) : pFound=0
        for uOff as uinteger = uBase to uBase+165535
          if *cptr(any ptr ptr ptr,uOff) = pSource2 then
            pFound = cptr(any ptr ptr,uOff) : exit for    
          end if
        next uOff
      else 'undo the hook
        pTarget = pSource2
      end if
      if pFound = 0 then return 0
      dim as DWORD OldProt = any
      VirtualProtect( pFound , 4 , PAGE_EXECUTE_READWRITE , @OldProt )
      *pFound = pTarget
      VirtualProtect( pFound , 4 , OldProt , @OldProt )
      FlushInstructionCache( GetCurrentProcess() , pFound , 4 )
      return pFound
    end function  
    function SetDIBitsToDevice_Detour( hdc as HDC , xDest as long , yDest as long , dwWidth as DWORD , dwHeight as DWORD , XSrc as long , YSrc as long , uStartScan as ulong , cScanLines as ulong , lpvBits as any ptr , lpBmi as BITMAPINFO ptr , fuColorUse as ulong ) as long
      'printf(!"update: h=%p xx=%i yy=%i w=%i h=%i x=%i y=%i t=%i n=%i\n",hdc,xDest,yDest,dwWidth,dwHeight,XSrc,YSrc,uStartScan,cScanlines)
      lpBmi->bmiHeader.biHeight = -dwHeight        
      var xD=(xDest*g_iCliWid)\g_iScrWid , yD=(yDest*g_iCliHei)\g_iScrHei
      var dW=(dwWidth*g_iCliWid)\g_iScrWid , dH=(dwHeight*g_iCliHei)\g_iScrHei
      return StretchDIBits(hdc,xD,yD,dw,dH,XSrc,YSrc,dwWidth,dwHeight,lpvBits,lpBmi,fuColorUse,SRCCOPY)
    end function
    static shared as any ptr pSetDIBitsToDevice_Detour = @SetDIBitsToDevice_Detour
  #endif
  
  declare sub Resize(iWid as integer=0,iHei as integer=0,iCenter as integer=1,iResizable as integer=0)  
  private function FbSubClass(hwnd as hwnd,iMsg as integer,wparam as wparam,lparam as lparam) as lresult
    select case iMsg 
    case WM_NCPAINT
      if g_iOffLeft orelse g_iOffTop then
        var hDC = GetWindowDC(hwnd)
        var hBrush = CreateSolidBrush(g_BorderColor)
        FillRgn(hDC,cast(HRGN,wparam),hBrush)
        DeleteObject(hBrush)
        ReleaseDC(hwnd,hDC)
      end if
    case WM_NCCALCSIZE
      if wParam then        
        var iResu = DefWindowProc(hWnd,iMsg,wParam,lParam)                        
        with *cptr(NCCALCSIZE_PARAMS ptr,lParam)          
          .rgrc(0).left  += g_iOffLeft : .rgrc(0).top    += iif(lScreenFlags and fb.GFX_OPENGL,0,g_iOffTop)
          .rgrc(0).right -= g_iOffLeft : .rgrc(0).bottom -= g_iOffTop                  
          'if (lScreenFlags and fb.GFX_OPENGL) then            
          '  var iWndWid = .rgrc(1).right-.rgrc(1).left, iWndHei = .rgrc(1).bottom-.rgrc(0).top
          '  var iCliWid = .rgrc(2).right-.rgrc(2).left, iCliHei = .rgrc(2).bottom-.rgrc(3).top
          '  g_iCliWid = iCliWid
          'end if
        end with        
        return iResu
      end if
    case WM_SIZE,WM_SIZING
      'if (lScreenFlags and fb.GFX_OPENGL) then        
        dim as RECT tRc : GetClientRect(hWnd,@tRc)
        g_iCliWid = tRc.right : g_iCliHei = tRc.bottom        
      'end if
      return DefWindowProc(hwnd,imsg,wparam,lparam)      
    case WM_SETCURSOR      
      'return DefWindowProc(hwnd,imsg,wparam,lparam)
    case WM_LBUTTONUP,WM_LBUTTONDOWN,WM_RBUTTONUP,WM_RBUTTONDOWN,WM_MBUTTONUP,WM_MBUTTONDOWN
      var xPos = cshort(LOWORD(lParam)),yPos = cshort(HIWORD(lParam))
      dim as rect fbcli = any: GetClientRect(hwnd,@fbcli)            
      xPos = cshort((xPos*g_iScrWid)\fbcli.Right)
      yPos = cshort((yPos*g_iScrHei)\fbcli.Bottom)      
      lParam = MAKELONG(cushort(xPos),cushort(yPos))
    case WM_LBUTTONDBLCLK,WM_RBUTTONDBLCLK,WM_MBUTTONDBLCLK
      var xPos = cshort(LOWORD(lParam)),yPos = cshort(HIWORD(lParam))
      dim as rect fbcli = any: GetClientRect(hwnd,@fbcli)            
      xPos = cshort((xPos*g_iScrWid)\fbcli.Right)
      yPos = cshort((yPos*g_iScrHei)\fbcli.Bottom)      
      lParam = MAKELONG(cushort(xPos),cushort(yPos))
    case WM_MOUSEMOVE      
      var xPos = cshort(LOWORD(lParam)),yPos = cshort(HIWORD(lParam))      
      dim as rect fbcli = any: GetClientRect(hwnd,@fbcli)            
      xPos = cshort((xPos*g_iScrWid)\fbcli.Right)
      yPos = cshort((yPos*g_iScrHei)\fbcli.Bottom)
      lParam = MAKELONG(cushort(xPos),cushort(yPos))
      'SetWindowText(hwnd,"Ok = " & g_iScrWid & "x" & g_iScrHei & " - " & fbcli.right & "x" & fbcli.bottom & " - " & xPos & "," & yPos)
    'case WM_NCHITTEST
    '  return HTTRANSPARENT
    case WM_NCHITTEST,WM_NCMOUSEMOVE,12,174,32            
      'return DefWindowProc(hwnd,imsg,wparam,lparam)
    case WM_SYSKEYDOWN       
      if wparam = VK_RETURN then
        if (lScreenFlags and fb.gfx_no_switch)=0 then
          PostMessage( hwnd , WM_USER+90 , 0 , 0 )
        end if
        return DefWindowProc(hwnd,imsg,wparam,lparam)
      else
        SendMessage( hwnd , WM_KEYDOWN , wparam , lparam )
      end if      
    case WM_SYSKEYUP
      if wparam <> VK_RETURN then SendMessage( hwnd , WM_KEYUP , wparam , lparam )
    'case WM_SYSCHAR
    '  if wparam <> VK_RETURN then SendMessage( hwnd , WM_CHAR , wparam , lparam )
    'case WM_SYSDEADCHAR
    '  if wparam <> VK_RETURN then SendMessage( hwnd , WM_DEADCHAR , wparam , lparam )
    case WM_PAINT
      ValidateRect(hwnd,NULL)      
    case WM_GETMINMAXINFO
      return DefWindowProc(hwnd,imsg,wparam,lparam)
    case WM_DESTROY,WM_CLOSE
      'return 0
    case WM_USER+90
      if lOldStyle then                                            
        g_Fullscreen = 0
        g_iOffLeft = 0 : g_iOffTop=0
        ShowWindow( hwnd , SW_RESTORE )        
        SetWindowLong( hwnd , GWL_STYLE , lOldStyle ) : lOldStyle = 0
        SetWindowPos( hwnd , HWND_NOTOPMOST , 0 , 0 , 0 , 0 , SWP_NOMOVE or SWP_NOSIZE or SWP_FRAMECHANGED)                
      else        
        g_Fullscreen = 1
        var iDeskWid = GetSystemMetrics(SM_CXSCREEN), iDeskHei = GetSystemMetrics(SM_CYSCREEN)        
        if g_iAspect then
          screeninfo g_iScrWid,g_iScrHei
          dim as long iWid,iHei
          iHei=iDeskHei : iWid=(iHei*g_iScrWid)\g_iScrHei
          if iWid>iDeskWid then
            iWid=iDeskWid : iHei=(iWid*g_iScrHei)\g_iScrWid
          end if
          g_iOffLeft = (iDeskWid-iWid)\2 : g_iOffTop = (iDeskHei-iHei)\2      
        else
          g_iOffLeft = 0 : g_iOffTop = 0
        end if
        lOldStyle = GetWindowLong( hwnd , GWL_STYLE ) or WS_VISIBLE
        SetWindowLong( hwnd , GWL_STYLE , WS_POPUP or WS_VISIBLE)
        ShowWindow( hwnd , SW_MAXIMIZE )
        SetWindowPos( hwnd , HWND_TOPMOST , 0 , 0 , 0 , 0 , SWP_NOMOVE or SWP_NOSIZE or SWP_FRAMECHANGED)        
        if g_iOffLeft orelse g_iOffTop then
          RedrawWindow( hwnd , NULL , NULL , RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE	or RDW_UPDATENOW or RDW_NOCHILDREN ) 	
        end if
        
      end if      
    case else
      'printf "%i(%x) ",iMsg,iMsg
      'return DefWindowProc(hwnd,imsg,wparam,lparam)
    
    end select
    if g_Temp then return DefWindowProc(hwnd,imsg,wparam,lparam)      
    return CallWindowProc(cast(any ptr,lOrgFbProc),hwnd,iMsg,wparam,lparam)
  end function
  sub Resize(iWid as integer=0,iHei as integer=0,iAspect as integer=1,iResizable as integer=0)    
    static fbWnd as hwnd, fbrct as rect, fbcli as rect
    static iDeskWid as integer,iDeskHei as integer    
    dim as hwnd newWnd
    Screencontrol(fb.get_window_handle,*cast(ulong ptr,@newWnd))      
    screeninfo g_iScrWid,g_iScrHei
    if newWnd<>fbWnd then
      fbWnd = newWnd
      lOrgFbProc = SetWindowLong(fbWnd,GWL_WNDPROC,clng(@FbSubClass))                    
    end if   
        
    var iStyle   = GetWindowLong(fbwnd,GWL_STYLE)
    if iResizable then iStyle or= WS_SIZEBOX else iStyle and= (not WS_THICKFRAME)    
    g_temp=1
    SetWindowLong(fbWnd,GWL_STYLE,iStyle)        
    SetWindowPos(fbWnd,0,0,0,0,0,SWP_NOSIZE or SWP_FRAMECHANGED or SWP_NOZORDER or SWP_NOMOVE)
    g_temp=0
    
    iDeskWid = GetSystemMetrics(SM_CXSCREEN)
    iDeskHei = GetSystemMetrics(SM_CYSCREEN)
    GetWindowRect(fbwnd,@fbrct)
    GetClientRect(fbwnd,@fbcli)
    
    var iSx = ((fbrct.right-fbrct.left)-(fbcli.right-fbcli.left))
    var iSy = ((fbrct.bottom-fbrct.top)-(fbcli.bottom-fbcli.top))
    var iWid2 = iWid+iSx , iHei2 = iHei+iSy    
    var iFlags = SWP_SHOWWINDOW or SWP_NOZORDER or SWP_FRAMECHANGED
    
    dim as long iLeft,iTop             
    if iWid<=0 orelse iHei<=0 then 
      iFlags and= (not SWP_NOZORDER)
      if iWid=0 then iWid2 = iDeskWid': iLeft=0
      if iHei=0 then iHei2 = iDeskHei': iTop=0
      if iWid<0 andalso iHei>=0 then
        iWid2=iSx+(((iHei2-iSy)*g_iScrWid)\g_iScrHei)      
      elseif iHei<0 andalso iWid>=0 then
        iHei2=iSy+(((iWid2-iSx)*g_iScrHei)\g_iScrWid)        
      end if      
    end if       
    
    if 1 then iLeft = (iDeskWid-iWid2)\2 : iTop = (iDeskHei-iHei2)\2      
    g_iAspect = iAspect : g_iOffLeft = 0 : g_iOffTop=0 : lOldStyle=0
    
    #ifndef NoGlResize
    if __fb_gl(5) <> @GlViewPortDetour then
      GlViewPortOrg = __fb_gl(5)
      __fb_gl(5) = @GlViewPortDetour
    end if
    #endif
    #ifndef NoGdiResize
      if g_pRevertGdi1 = 0 then        
        if lcase(g_sGfxDriver)="gdi" then
          g_pRevertGdi1 = AutoPatch( @SetDIBitsToDevice , GfxWin32(289) , @pSetDIBitsToDevice_Detour )
          g_pRevertGdi2 = AutoPatch( @SetDIBitsToDevice , GfxWin32(289) , @pSetDIBitsToDevice_Detour )
        end if
      end if
    #endif
        
    SetWindowPos(fbWnd,HWND_TOPMOST,iLeft,iTop,iWid2,iHei2,iFlags)
    'end if
    
    if g_Fullscreen=0 andalso g_lFirstSize andalso (lScreenFlags and fb.gfx_fullscreen) then
      SendMessage(fbWnd,WM_USER+90,0,0)
    end if
    
  end sub      

  dim shared PreDetour as any ptr,llDetour as ulongint
  declare sub PreResize(iUndo as integer=0)  
  private sub AutoUnDetour() destructor        
    if PreDetour then PreResize(1): PreDetour=0    
    if g_pRevertGdi1 then AutoPatch( @SetDIBitsToDevice , g_pRevertGdi1 , 0 ) : g_pRevertGdi1 = 0
    if g_pRevertGdi2 then AutoPatch( @SetDIBitsToDevice , g_pRevertGdi1 , 0 ) : g_pRevertGdi2 = 0    
  end sub   
  const NOTVIS = (not WS_VISIBLE)
  const CANRES = WS_THICKFRAME
  const BETRANS = WS_EX_TRANSPARENT
  const NOTASK = WS_EX_TOOLWINDOW
  sub CreateWindowExADetour naked alias "CreateWindowExADetour" ()
    asm      
      mov eax,[PreDetour]
      pusha
      push 1
      call PreResize
      popa            
      and dword ptr [esp+16+4], NOTVIS
      or dword ptr [esp+16+4], CANRES      
      #ifdef NoTaskbar
      or dword ptr [esp+0+4], NOTASK
      #endif
      #ifdef JustLayer
      or dword ptr [esp+0+4], BETRANS
      #endif
      push ebp
      mov ebp,esp
      jmp eax
    end asm
  end sub     
  sub PreResize(iUndo as integer=0)    
    var pPtr = cast(any ptr,GetProcAddress(GetModuleHandle("user32.dll"),"CreateWindowExA"))
    dim as integer OldProt = any    
    VirtualProtect(pPtr,8,PAGE_READWRITE,@OldProt)      
    if iUndo then
      if PreDetour then
        'puts("undo")
        *cptr(ulongint ptr,pPtr) = llDetour
        PreDetour = 0
      end if
    else              
      if PreDetour=0 then                  
        'puts("detour")
        PreDetour = pPtr+5 'mov esi | push ebp | mov ebp,esp
        llDetour = *cptr(ulongint ptr,pPtr)
        *cptr(ubyte ptr,pPtr+0) = &hE9
        *cptr(ulong ptr,pPtr+1) = clng(@CreateWindowExADetour)-clng(pPtr+5)
      end if
    end if
    VirtualProtect(pPtr,8,OldProt,@OldProt)
    FlushInstructionCache(GetCurrentProcess(),pPtr,8)
  end sub  
  
  sub BorderColor( iR as ulong , iG as ulong , iB as ulong )
    g_BorderColor = BGR(iR,iG,iB)
    if g_iAspect andalso (g_iOffLeft orelse g_iOffTop) then
      dim as HWND newWnd
      Screencontrol(fb.get_window_handle,*cast(ulong ptr,@newWnd))      
      if newWnd then
        RedrawWindow( newWnd , NULL , NULL , RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE	or RDW_UPDATENOW or RDW_NOCHILDREN ) 	
      end if
    end if
  end sub
  
end namespace

#define BeforeScreenRes PreResize

sub _screenres(iWid as long,iHei as long,depth as long=8,num_pages as long=1,flags as long=0,refresh_rate as long=0)  
  if gfx.PreDetour then
    gfx.lScreenflags = flags : gfx.g_lFirstSize = 1 : gfx.g_BorderColor = 0 : gfx.g_Fullscreen = 0
    Flags = (flags and (not fb.gfx_fullscreen))' or fb.gfx_No_Switch
  else
    gfx.lScreenflags=0
  end if
  gfx.g_iCliWid = 0: gfx.g_iCliHei=0   
  if (flags and fb.gfx_shaped_window) then screencontrol(fb.SET_DRIVER_NAME,"GDI")    
  screenres iWid,iHei,depth,num_pages,flags,refresh_rate  
  if (flags and fb.gfx_shaped_window) then screencontrol(fb.SET_DRIVER_NAME,"")    
  screencontrol(fb.GET_DRIVER_NAME,gfx.g_sGfxDriver)
end sub
#undef screenres
#define screenres _screenres

#define _UNUSED -32768
function _SetMouse( x As long = _UNUSED, y As long = _UNUSED, visibility As long = _UNUSED, clip As long = _UNUSED ) As long
  dim as HWND gfxWnd : Screencontrol(fb.get_window_handle,*cast(uinteger ptr,@gfxWnd))      
  if gfxWnd=0 then return 1
  dim as RECT tRC = any : GetClientRect(gfxWnd,@tRC)  
  if x<>_UNUSED andalso y <> _UNUSED then     
    if cuint(x)>=gfx.g_iScrWid orelse cuint(y)>=gfx.g_iScrHei then return 1
    dim as POINT tPT = ( (x*((tRC.right)-0))\gfx.g_iScrWid , (y*((tRC.bottom)-0))\gfx.g_iScrHei )
    ClientToScreen(gfxWnd,@tPT) : SetCursorPos( tPT.x , tPT.y )
  end if  
  if visibility <> _UNUSED then SetMouse ,,abs(visibility)
  if clip <> _UNUSED then     
    if GetForegroundWindow()=gfxWnd then
      ClientToScreen(gfxWnd,cast(point ptr,@tRC))
      ClientToScreen(gfxWnd,cast(point ptr,@tRC)+1)
      if clip then ClipCursor(@tRC) else ClipCursor(NULL)
    else
      ClipCursor(NULL)
    end if
  end if
end function
#undef setmouse
#define setmouse _setmouse
  

